From f8e8ad64b94bc3d1d33cbbb589e7b42d5e2ac30e Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Thu, 22 May 2025 11:45:18 -0300 Subject: [PATCH] fix: fix regex on mcp server tab test (#8175) * Fixed MCP Server Tab Test * Fixed mcp server tab test * Added timeout to test * Added retry to mcp server tab test --- .../extended/features/mcp-server-tab.spec.ts | 435 +++++++++--------- 1 file changed, 228 insertions(+), 207 deletions(-) diff --git a/src/frontend/tests/extended/features/mcp-server-tab.spec.ts b/src/frontend/tests/extended/features/mcp-server-tab.spec.ts index c31153fe2..1ba3955c9 100644 --- a/src/frontend/tests/extended/features/mcp-server-tab.spec.ts +++ b/src/frontend/tests/extended/features/mcp-server-tab.spec.ts @@ -6,224 +6,245 @@ test( "user should be able to manage MCP server actions and configuration", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - try { - await awaitBootstrapTest(page); + const maxRetries = 5; - // Create a new flow - await page.getByTestId("blank-flow").click(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("api request"); + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + console.log(`Attempt ${attempt} of ${maxRetries}`); - await page.waitForSelector('[data-testid="dataAPI Request"]', { - timeout: 3000, - }); + await awaitBootstrapTest(page); - await page - .getByTestId("dataAPI Request") - .hover() - .then(async () => { - await page.getByTestId("add-component-button-api-request").click(); - }); + // Create a new flow + await page.getByTestId("blank-flow").click(); + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("api request"); - await page.waitForSelector( - '[data-testid="generic-node-title-arrangement"]', - { + await page.waitForSelector('[data-testid="dataAPI Request"]', { timeout: 3000, - }, - ); - - await page.getByTestId("generic-node-title-arrangement").click(); - - // Exit the flow - await page.getByTestId("icon-ChevronLeft").last().click(); - - // Navigate to MCP server tab - await page.getByTestId("mcp-btn").click(); - - // Verify MCP server tab is visible - await expect(page.getByTestId("mcp-server-title")).toBeVisible(); - await expect(page.getByText("Flows/Actions")).toBeVisible(); - - // Click on Edit Actions button - await page.getByTestId("button_open_actions").click(); - await page.waitForTimeout(500); - - // Verify actions modal is open - await expect(page.getByText("MCP Server Actions")).toBeVisible(); - - // Select some actions - const rowsCount = await page.getByRole("row").count(); - expect(rowsCount).toBeGreaterThan(0); - - const cellsCount = await page.getByRole("gridcell").count(); - expect(cellsCount).toBeGreaterThan(0); - - await page.getByRole("gridcell").first().click(); - await page.waitForTimeout(500); - - const isChecked = await page - .locator('input[data-ref="eInput"]') - .first() - .isChecked(); - - if (!isChecked) { - await page.locator('input[data-ref="eInput"]').first().click(); - await page.waitForTimeout(500); - } - const isCheckedAgain = await page - .locator('input[data-ref="eInput"]') - .first() - .isChecked(); - - if (isCheckedAgain) { - await page.locator('input[data-ref="eInput"]').first().click(); - await page.waitForTimeout(500); - } - - const isCheckedAgainAgain = await page - .locator('input[data-ref="eInput"]') - .first() - .isChecked(); - - expect(isCheckedAgainAgain).toBeFalsy(); - - // Select first action - await page - .locator('input[data-ref="eInput"]') - .nth(rowsCount + 1) - .click(); - await page.waitForTimeout(500); - - await page - .getByRole("gridcell") - .nth(cellsCount - 1) - .click(); - await page.waitForTimeout(500); - - const isLastChecked = await page - .locator('input[data-ref="eInput"]') - .nth(rowsCount + 1) - .isChecked(); - - expect(isLastChecked).toBeTruthy(); - - expect( - await page.locator('[data-testid="input_update_name"]').isVisible(), - ).toBe(true); - - await page.getByTestId("input_update_name").fill("mcp test name"); - await page.waitForTimeout(500); - - // Close the modal - await page.getByText("Close").last().click(); - await page.waitForTimeout(500); - - // Verify the selected action is visible in the tab - await expect(page.getByTestId("div-mcp-server-tools")).toBeVisible(); - - // Generate API key if not in auto login mode - const isAutoLogin = await page.getByText("Generate API key").isVisible(); - if (isAutoLogin) { - await page.getByText("Generate API key").click(); - await expect(page.getByText("API key generated")).toBeVisible(); - } - - // Copy configuration - await page.getByTestId("icon-copy").click(); - await expect(page.getByTestId("icon-check")).toBeVisible(); - - // Get the SSE URL from the configuration - const configJson = await page.locator("pre").textContent(); - expect(configJson).toContain("mcpServers"); - expect(configJson).toContain("supergateway"); - expect(configJson).toContain("sse"); - - // Extract the SSE URL from the configuration - const sseUrlMatch = configJson?.match( - /"args":\s*\[\s*"-y",\s*"supergateway",\s*"--sse",\s*"([^"]+)"/, - ); - expect(sseUrlMatch).not.toBeNull(); - const sseUrl = sseUrlMatch![1]; - - // Verify setup guide link - await expect(page.getByText("setup guide")).toBeVisible(); - await expect(page.getByText("setup guide")).toHaveAttribute( - "href", - "https://docs.langflow.org/mcp-server#connect-clients-to-use-the-servers-actions", - ); - - await awaitBootstrapTest(page); - - // Create a new flow with MCP component - await page.getByTestId("blank-flow").click(); - - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("mcp connection"); - - await page.waitForSelector('[data-testid="toolsMCP Connection"]', { - timeout: 30000, - }); - - await page - .getByTestId("toolsMCP Connection") - .dragTo(page.locator('//*[@id="react-flow-id"]'), { - targetPosition: { x: 0, y: 0 }, }); - await page.getByTestId("fit_view").click(); - await zoomOut(page, 3); + await page + .getByTestId("dataAPI Request") + .hover() + .then(async () => { + await page.getByTestId("add-component-button-api-request").click(); + }); - // Switch to SSE tab and paste the URL - await page.getByTestId("tab_1_sse").click(); - - await page.waitForSelector('[data-testid="textarea_str_sse_url"]', { - state: "visible", - timeout: 30000, - }); - - await page.getByTestId("textarea_str_sse_url").fill(sseUrl); - await page.waitForTimeout(2000); - - // Wait for the tools to become available - let attempts = 0; - const maxAttempts = 3; - let dropdownEnabled = false; - - while (attempts < maxAttempts && !dropdownEnabled) { - await page.getByTestId("refresh-button-sse_url").click(); - - try { - await page.waitForSelector( - '[data-testid="dropdown_str_tool"]:not([disabled])', - { - timeout: 10000, - state: "visible", - }, - ); - dropdownEnabled = true; - } catch (error) { - attempts++; - console.log(`Retry attempt ${attempts} for refresh button`); - } - } - - if (!dropdownEnabled) { - test.skip( - true, - "Dropdown did not become enabled after multiple refresh attempts", + await page.waitForSelector( + '[data-testid="generic-node-title-arrangement"]', + { + timeout: 3000, + }, ); + + await page.getByTestId("generic-node-title-arrangement").click(); + + // Exit the flow + await page.getByTestId("icon-ChevronLeft").last().click(); + + // Navigate to MCP server tab + await page.getByTestId("mcp-btn").click(); + + // Verify MCP server tab is visible + await expect(page.getByTestId("mcp-server-title")).toBeVisible(); + await expect(page.getByText("Flows/Actions")).toBeVisible(); + + // Click on Edit Actions button + await page.getByTestId("button_open_actions").click(); + await page.waitForTimeout(500); + + // Verify actions modal is open + await expect(page.getByText("MCP Server Actions")).toBeVisible(); + + // Select some actions + const rowsCount = await page.getByRole("row").count(); + expect(rowsCount).toBeGreaterThan(0); + + const cellsCount = await page.getByRole("gridcell").count(); + expect(cellsCount).toBeGreaterThan(0); + + await page.getByRole("gridcell").first().click(); + await page.waitForTimeout(500); + + const isChecked = await page + .locator('input[data-ref="eInput"]') + .first() + .isChecked(); + + if (!isChecked) { + await page.locator('input[data-ref="eInput"]').first().click(); + await page.waitForTimeout(500); + } + const isCheckedAgain = await page + .locator('input[data-ref="eInput"]') + .first() + .isChecked(); + + if (isCheckedAgain) { + await page.locator('input[data-ref="eInput"]').first().click(); + await page.waitForTimeout(500); + } + + const isCheckedAgainAgain = await page + .locator('input[data-ref="eInput"]') + .first() + .isChecked(); + + expect(isCheckedAgainAgain).toBeFalsy(); + + // Select first action + await page + .locator('input[data-ref="eInput"]') + .nth(rowsCount + 1) + .click(); + await page.waitForTimeout(500); + + await page + .getByRole("gridcell") + .nth(cellsCount - 1) + .click(); + await page.waitForTimeout(500); + + const isLastChecked = await page + .locator('input[data-ref="eInput"]') + .nth(rowsCount + 1) + .isChecked(); + + expect(isLastChecked).toBeTruthy(); + + expect( + await page.locator('[data-testid="input_update_name"]').isVisible(), + ).toBe(true); + + await page.getByTestId("input_update_name").fill("mcp test name"); + await page.waitForTimeout(500); + + // Close the modal + await page.getByText("Close").last().click(); + await page.waitForTimeout(500); + + // Verify the selected action is visible in the tab + await expect(page.getByTestId("div-mcp-server-tools")).toBeVisible(); + + // Generate API key if not in auto login mode + const isAutoLogin = await page + .getByText("Generate API key") + .isVisible(); + if (isAutoLogin) { + await page.getByText("Generate API key").click(); + await expect(page.getByText("API key generated")).toBeVisible(); + } + + // Copy configuration + await page.getByTestId("icon-copy").click(); + await expect(page.getByTestId("icon-check")).toBeVisible(); + + // Get the SSE URL from the configuration + const configJson = await page.locator("pre").textContent(); + expect(configJson).toContain("mcpServers"); + expect(configJson).toContain("mcp-proxy"); + expect(configJson).toContain("uvx"); + + // Extract the SSE URL from the configuration + const sseUrlMatch = configJson?.match( + /"args":\s*\[\s*"mcp-proxy"\s*,\s*"([^"]+)"/, + ); + expect(sseUrlMatch).not.toBeNull(); + const sseUrl = sseUrlMatch![1]; + + // Verify setup guide link + await expect(page.getByText("setup guide")).toBeVisible(); + await expect(page.getByText("setup guide")).toHaveAttribute( + "href", + "https://docs.langflow.org/mcp-server#connect-clients-to-use-the-servers-actions", + ); + + await awaitBootstrapTest(page); + + // Create a new flow with MCP component + await page.getByTestId("blank-flow").click(); + + await page.getByTestId("sidebar-search-input").click(); + await page.getByTestId("sidebar-search-input").fill("mcp connection"); + + await page.waitForSelector('[data-testid="toolsMCP Connection"]', { + timeout: 30000, + }); + + await page + .getByTestId("toolsMCP Connection") + .dragTo(page.locator('//*[@id="react-flow-id"]'), { + targetPosition: { x: 0, y: 0 }, + }); + + await page.getByTestId("fit_view").click(); + await zoomOut(page, 3); + + // Switch to SSE tab and paste the URL + await page.getByTestId("tab_1_sse").click(); + + await page.waitForSelector('[data-testid="textarea_str_sse_url"]', { + state: "visible", + timeout: 30000, + }); + await page.waitForTimeout(2000); + + await page.getByTestId("textarea_str_sse_url").fill(""); + await page.getByTestId("textarea_str_sse_url").fill(sseUrl); + + await page.waitForTimeout(2000); + + // Wait for the tools to become available + let attempts = 0; + const maxAttempts = 3; + let dropdownEnabled = false; + + while (attempts < maxAttempts && !dropdownEnabled) { + await page.getByTestId("refresh-button-sse_url").click(); + + try { + await page.waitForSelector( + '[data-testid="dropdown_str_tool"]:not([disabled])', + { + timeout: 10000, + state: "visible", + }, + ); + dropdownEnabled = true; + } catch (error) { + attempts++; + console.log(`Retry attempt ${attempts} for refresh button`); + } + } + + if (!dropdownEnabled) { + throw new Error( + "Dropdown did not become enabled after multiple refresh attempts", + ); + } + + // Verify tools are available + await page.getByTestId("dropdown_str_tool").click(); + await page.waitForTimeout(2000); + + const fetchOptionCount = await page.getByText("mcp_test_name").count(); + expect(fetchOptionCount).toBeGreaterThan(0); + + // If we get here, the test passed + console.log(`Test passed on attempt ${attempt}`); return; + } catch (error) { + error = error as Error; + console.log(`Attempt ${attempt} failed:`, error); + + if (attempt === maxRetries) { + console.log(`All ${maxRetries} attempts failed. Last error:`, error); + throw error; + } + + // Wait a bit before retrying + await page.waitForTimeout(2000); } - - // Verify tools are available - await page.getByTestId("dropdown_str_tool").click(); - await page.waitForTimeout(2000); - - const fetchOptionCount = await page.getByText("mcp_test_name").count(); - expect(fetchOptionCount).toBeGreaterThan(0); - } catch (error) { - console.log(`Test failed with error: ${error.message}`); - test.skip(true, `Skipping test due to error: ${error.message}`); } }, );