mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-24 08:57:31 +08:00
test(mcp-frontend): Improve test reliability and reduce code duplication (#11232)
* fix all mcp server tests * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * add fallback to tavily api key * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
24db9b9e5a
commit
49ed55fbea
@ -187,7 +187,11 @@ export default function McpComponent({
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Button size="sm" onClick={handleAddButtonClick}>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleAddButtonClick}
|
||||
data-testid="add-mcp-server-simple-button"
|
||||
>
|
||||
<span>Add MCP Server</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -36,25 +36,39 @@ withEventDeliveryModes(
|
||||
|
||||
await initialGPTsetup(page);
|
||||
|
||||
// Find the Tavily node and fill the API key with retry
|
||||
const tavilyNode = page.getByTestId(/rf__node-TavilySearchComponent-/);
|
||||
const tavilyApiKeyInput = tavilyNode.getByTestId("popover-anchor-input-api_key");
|
||||
// Fill Tavily API key - try multiple approaches for robustness
|
||||
const tavilyApiKey = process.env.TAVILY_API_KEY ?? "";
|
||||
|
||||
const maxRetries = 5;
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
await tavilyApiKeyInput.waitFor({ state: "visible", timeout: 10000 });
|
||||
await tavilyApiKeyInput.click();
|
||||
await tavilyApiKeyInput.clear();
|
||||
await tavilyApiKeyInput.fill(process.env.TAVILY_API_KEY ?? "");
|
||||
await tavilyApiKeyInput.press("Tab"); // Trigger blur to save value
|
||||
|
||||
// Verify the value was filled
|
||||
const value = await tavilyApiKeyInput.inputValue();
|
||||
if (value === process.env.TAVILY_API_KEY) {
|
||||
break;
|
||||
// Approach 1: Direct fill like Instagram Copywriter (most reliable)
|
||||
try {
|
||||
await page
|
||||
.getByTestId(/rf__node-TavilySearchComponent-[A-Za-z0-9]{5}/)
|
||||
.getByTestId("popover-anchor-input-api_key")
|
||||
.nth(0)
|
||||
.fill(tavilyApiKey, { timeout: 10000 });
|
||||
} catch {
|
||||
// Approach 2: Try without the node prefix, by index
|
||||
try {
|
||||
const apiKeyInputs = page.getByTestId("popover-anchor-input-api_key");
|
||||
const count = await apiKeyInputs.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const input = apiKeyInputs.nth(i);
|
||||
const placeholder = await input.getAttribute("placeholder");
|
||||
if (
|
||||
placeholder?.toLowerCase().includes("tavily") ||
|
||||
i === count - 1
|
||||
) {
|
||||
await input.fill(tavilyApiKey);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Approach 3: Last resort - fill all api_key inputs with Tavily key
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-api_key")
|
||||
.last()
|
||||
.fill(tavilyApiKey);
|
||||
}
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
await page
|
||||
|
||||
@ -125,7 +125,10 @@ test(
|
||||
await page.keyboard.press(`ControlOrMeta+V`);
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
// Wait for error message to appear
|
||||
await expect(page.getByText("Server already exists.")).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const numberOfErrors = await page
|
||||
.getByText("Server already exists.")
|
||||
|
||||
@ -1,405 +1,291 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { adjustScreenView } from "../../utils/adjust-screen-view";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
import { zoomOut } from "../../utils/zoom-out";
|
||||
import { openAddMcpServerModal } from "../../utils/open-add-mcp-server-modal";
|
||||
|
||||
test(
|
||||
"user should be able to manage MCP server tools and configuration",
|
||||
{ tag: ["@release", "@workspace", "@components"] },
|
||||
async ({ page }) => {
|
||||
const maxRetries = 5;
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
console.warn(`Attempt ${attempt} of ${maxRetries}`);
|
||||
// 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 awaitBootstrapTest(page);
|
||||
await page.waitForSelector('[data-testid="data_sourceAPI Request"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// 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");
|
||||
// Use dragTo which is more reliable than click on add-component-button
|
||||
await page
|
||||
.getByTestId("data_sourceAPI Request")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||
|
||||
await page.waitForSelector('[data-testid="data_sourceAPI Request"]', {
|
||||
timeout: 3000,
|
||||
});
|
||||
await page.waitForSelector(
|
||||
'[data-testid="generic-node-title-arrangement"]',
|
||||
{
|
||||
timeout: 30000,
|
||||
},
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId("data_sourceAPI Request")
|
||||
.hover()
|
||||
.then(async () => {
|
||||
await page.getByTestId("add-component-button-api-request").click();
|
||||
});
|
||||
// Exit the flow
|
||||
await page.getByTestId("icon-ChevronLeft").last().click();
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="generic-node-title-arrangement"]',
|
||||
{
|
||||
timeout: 3000,
|
||||
},
|
||||
);
|
||||
// Navigate to MCP server tab
|
||||
await page.getByTestId("mcp-btn").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId("generic-node-title-arrangement").click();
|
||||
// Verify MCP server tab is visible
|
||||
await expect(page.getByTestId("mcp-server-title")).toBeVisible();
|
||||
await expect(page.getByText("Flows/Tools")).toBeVisible();
|
||||
|
||||
// Exit the flow
|
||||
await page.getByTestId("icon-ChevronLeft").last().click();
|
||||
// Click on Edit Tools button
|
||||
await page.getByTestId("button_open_actions").click();
|
||||
|
||||
// Navigate to MCP server tab
|
||||
await page.getByTestId("mcp-btn").click();
|
||||
// Verify actions modal is open
|
||||
await expect(page.getByText("MCP Server Tools")).toBeVisible();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify MCP server tab is visible
|
||||
await expect(page.getByTestId("mcp-server-title")).toBeVisible();
|
||||
await expect(page.getByText("Flows/Tools")).toBeVisible();
|
||||
await page.waitForSelector("text=Flow Name", { timeout: 30000 });
|
||||
|
||||
// Click on Edit Tools button
|
||||
await page.getByTestId("button_open_actions").click();
|
||||
await page.waitForTimeout(500);
|
||||
// Select some actions
|
||||
const rowsCount = await page.getByRole("row").count();
|
||||
expect(rowsCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify actions modal is open
|
||||
await expect(page.getByText("MCP Server Tools")).toBeVisible();
|
||||
const cellsCount = await page.getByRole("gridcell").count();
|
||||
expect(cellsCount).toBeGreaterThan(0);
|
||||
|
||||
await page.waitForSelector("text=Flow Name", { timeout: 3000 });
|
||||
await page.getByRole("gridcell").first().click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Select some actions
|
||||
const rowsCount = await page.getByRole("row").count();
|
||||
expect(rowsCount).toBeGreaterThan(0);
|
||||
const checkbox = page.locator('input[data-ref="eInput"]').first();
|
||||
|
||||
const cellsCount = await page.getByRole("gridcell").count();
|
||||
expect(cellsCount).toBeGreaterThan(0);
|
||||
|
||||
await page.getByRole("gridcell").first().click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
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(1000);
|
||||
}
|
||||
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(1000);
|
||||
}
|
||||
|
||||
// Verify if the state is maintained
|
||||
|
||||
await page.locator('input[data-ref="eInput"]').first().click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Close the modal
|
||||
await page.getByText("Close").last().click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Navigate to MCP server tab
|
||||
await page.getByTestId("mcp-btn").click({ timeout: 10000 });
|
||||
|
||||
// Verify MCP server tab is visible
|
||||
await expect(page.getByTestId("mcp-server-title")).toBeVisible();
|
||||
await expect(page.getByText("Flows/Tools")).toBeVisible();
|
||||
|
||||
// Click on Edit Tools button
|
||||
await page.getByTestId("button_open_actions").click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verify actions modal is open
|
||||
await expect(page.getByText("MCP Server Tools")).toBeVisible();
|
||||
|
||||
const persistedCheckbox = page
|
||||
.locator('input[data-ref="eInput"]')
|
||||
.first();
|
||||
|
||||
if (!(await persistedCheckbox.isChecked())) {
|
||||
await persistedCheckbox.click();
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
|
||||
await expect(persistedCheckbox).toBeChecked();
|
||||
|
||||
await page.locator('input[data-ref="eInput"]').first().click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Select first action
|
||||
let element = page.locator('input[data-ref="eInput"]').last();
|
||||
let elementText = await element.getAttribute("id");
|
||||
|
||||
await element.scrollIntoViewIfNeeded();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const count = 0;
|
||||
|
||||
while (
|
||||
elementText !==
|
||||
(await page
|
||||
.locator('input[data-ref="eInput"]')
|
||||
.last()
|
||||
.getAttribute("id")) &&
|
||||
count < 20
|
||||
) {
|
||||
element = page.locator('input[data-ref="eInput"]').last();
|
||||
elementText = await element.getAttribute("id");
|
||||
await element.scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
await page.locator('input[data-ref="eInput"]').last().click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const isLastChecked = await page
|
||||
.locator('input[data-ref="eInput"]')
|
||||
.last()
|
||||
.isChecked();
|
||||
|
||||
expect(isLastChecked).toBeTruthy();
|
||||
|
||||
await page
|
||||
.getByRole("gridcell")
|
||||
.nth(cellsCount - 1)
|
||||
.click();
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
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(2000);
|
||||
|
||||
// Close the modal
|
||||
await page.getByText("Close").last().click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Verify the selected action is visible in the tab
|
||||
await expect(page.getByTestId("div-mcp-server-tools")).toBeVisible();
|
||||
|
||||
// Switch to JSON mode
|
||||
await page.getByText("JSON", { exact: true }).last().click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 3000 });
|
||||
|
||||
// Test API key generation in JSON mode
|
||||
const generateApiKeyButton = page.getByText("Generate API key");
|
||||
const isGenerateButtonVisible = await generateApiKeyButton
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
if (isGenerateButtonVisible) {
|
||||
// Get the JSON configuration before generating
|
||||
const preElement = page.locator("pre").first();
|
||||
const jsonBeforeGeneration = await preElement.textContent();
|
||||
|
||||
// Verify "YOUR_API_KEY" is present in the JSON before generation
|
||||
expect(jsonBeforeGeneration).toContain("YOUR_API_KEY");
|
||||
|
||||
// Verify the button is visible and clickable
|
||||
await expect(generateApiKeyButton).toBeVisible();
|
||||
await expect(generateApiKeyButton).toBeEnabled();
|
||||
|
||||
// Click the Generate API key button
|
||||
await generateApiKeyButton.click();
|
||||
|
||||
// Wait for the API key to be generated and verify the state change
|
||||
// The button text should change from "Generate API key" to "API key generated"
|
||||
await expect(page.getByText("API key generated")).toBeVisible({
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
// Wait for the JSON to update - it should no longer contain "YOUR_API_KEY"
|
||||
// Retry until the JSON no longer contains "YOUR_API_KEY"
|
||||
let jsonAfterGeneration = await preElement.textContent();
|
||||
let retries = 0;
|
||||
while (
|
||||
jsonAfterGeneration?.includes("YOUR_API_KEY") &&
|
||||
retries < 10
|
||||
) {
|
||||
await page.waitForTimeout(500);
|
||||
jsonAfterGeneration = await preElement.textContent();
|
||||
retries++;
|
||||
}
|
||||
|
||||
// Verify "YOUR_API_KEY" is no longer present
|
||||
expect(jsonAfterGeneration).not.toContain("YOUR_API_KEY");
|
||||
|
||||
// Verify that an actual API key (not "YOUR_API_KEY") is present
|
||||
// The JSON format has: "--headers", "x-api-key", "<api-key-value>"
|
||||
// Match for "x-api-key" followed by comma and whitespace/newlines, then the API key
|
||||
const apiKeyMatch = jsonAfterGeneration?.match(
|
||||
/"x-api-key"[\s,]*"([^"]+)"/,
|
||||
);
|
||||
expect(apiKeyMatch).not.toBeNull();
|
||||
if (apiKeyMatch) {
|
||||
const generatedApiKey = apiKeyMatch[1];
|
||||
expect(generatedApiKey).not.toBe("YOUR_API_KEY");
|
||||
expect(generatedApiKey.length).toBeGreaterThan(0);
|
||||
// API keys should be non-empty strings
|
||||
expect(generatedApiKey.trim().length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
// Verify the Generate API key button text is no longer visible
|
||||
// (it should be replaced by "API key generated")
|
||||
await expect(generateApiKeyButton).not.toBeVisible();
|
||||
} else {
|
||||
// If button is not visible, verify we're in a valid state:
|
||||
// Either "API key generated" is shown (already generated) or
|
||||
// we're in auto-login mode (no API key needed)
|
||||
const apiKeyGeneratedText = page.getByText("API key generated");
|
||||
const hasApiKeyGenerated = await apiKeyGeneratedText
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
// In auto-login mode, neither button should be visible, which is expected
|
||||
// In API key mode with key already generated, "API key generated" should be visible
|
||||
expect(
|
||||
hasApiKeyGenerated ||
|
||||
!(await page.getByText("Generate API key").isVisible()),
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
// 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.evaluate(() => {
|
||||
return navigator.clipboard.readText();
|
||||
});
|
||||
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*"\/c"\s*,\s*"uvx"\s*,\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatch).not.toBeNull();
|
||||
const _sseUrl = sseUrlMatch![1];
|
||||
|
||||
await page.getByText("macOS/Linux", { exact: true }).click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 3000 });
|
||||
// Copy configuration
|
||||
await page.getByTestId("icon-copy").click();
|
||||
await expect(page.getByTestId("icon-check")).toBeVisible();
|
||||
|
||||
const configJsonLinux = await page.evaluate(() => {
|
||||
return navigator.clipboard.readText();
|
||||
});
|
||||
|
||||
const sseUrlMatchLinux = configJsonLinux?.match(
|
||||
/"args":\s*\[\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatchLinux).not.toBeNull();
|
||||
|
||||
// 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");
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="models_and_agentsMCP Tools"]',
|
||||
{
|
||||
timeout: 30000,
|
||||
},
|
||||
);
|
||||
|
||||
await page
|
||||
.getByTestId("models_and_agentsMCP Tools")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 50, y: 50 },
|
||||
});
|
||||
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page
|
||||
.getByTestId("mcp-server-dropdown")
|
||||
.click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.waitForSelector('[data-testid="json-input"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const randomSuffix = Math.floor(Math.random() * 90000) + 10000; // 5-digit random number
|
||||
const testName = `test_server_${randomSuffix}`;
|
||||
|
||||
await page
|
||||
.getByTestId("json-input")
|
||||
.fill(configJsonLinux.replace(/lf-starter_project/g, testName) || "");
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
const fetchOptionCount = await page.getByText("mcp_test_name").count();
|
||||
|
||||
expect(fetchOptionCount).toBeGreaterThan(0);
|
||||
|
||||
// If we get here, the test passed
|
||||
console.warn(`Test passed on attempt ${attempt}`);
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(`Attempt ${attempt} failed:`, error);
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
console.error(
|
||||
`All ${maxRetries} attempts failed. Last error:`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Wait a bit before retrying
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
// Toggle checkbox to ensure it ends up checked
|
||||
if (await checkbox.isChecked()) {
|
||||
await checkbox.click();
|
||||
}
|
||||
await checkbox.click();
|
||||
await expect(checkbox).toBeChecked();
|
||||
|
||||
// Close the modal
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
// Wait for modal to close
|
||||
await expect(page.getByText("MCP Server Tools")).not.toBeVisible();
|
||||
|
||||
await page.reload();
|
||||
|
||||
// Navigate to MCP server tab
|
||||
await page.getByTestId("mcp-btn").click({ timeout: 10000 });
|
||||
|
||||
// Verify MCP server tab is visible
|
||||
await expect(page.getByTestId("mcp-server-title")).toBeVisible();
|
||||
await expect(page.getByText("Flows/Tools")).toBeVisible();
|
||||
|
||||
// Click on Edit Tools button
|
||||
await page.getByTestId("button_open_actions").click();
|
||||
|
||||
// Verify actions modal is open
|
||||
await expect(page.getByText("MCP Server Tools")).toBeVisible();
|
||||
|
||||
// Wait for the grid to load
|
||||
await page.waitForSelector("text=Flow Name", { timeout: 30000 });
|
||||
|
||||
// AG Grid data rows have class .ag-row (header rows don't)
|
||||
// Get the first data row's checkbox
|
||||
const firstDataRowCheckbox = page
|
||||
.locator(".ag-row")
|
||||
.first()
|
||||
.locator('input[type="checkbox"]');
|
||||
|
||||
// Click to select the row
|
||||
if (!(await firstDataRowCheckbox.isChecked())) {
|
||||
await firstDataRowCheckbox.click();
|
||||
}
|
||||
await expect(firstDataRowCheckbox).toBeChecked({ timeout: 10000 });
|
||||
|
||||
// Click on the first cell of the first data row to open the sidebar for editing
|
||||
await page.locator(".ag-row").first().locator(".ag-cell").first().click();
|
||||
|
||||
await expect(page.locator('[data-testid="input_update_name"]')).toBeVisible(
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
await page.getByTestId("input_update_name").fill("mcp test name");
|
||||
|
||||
// Close the modal
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
// Wait for modal to close
|
||||
await expect(page.getByText("MCP Server Tools")).not.toBeVisible();
|
||||
|
||||
// Verify the selected action is visible in the tab
|
||||
await expect(page.getByTestId("div-mcp-server-tools")).toBeVisible();
|
||||
|
||||
// Switch to JSON mode
|
||||
await page.getByText("JSON", { exact: true }).last().click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 30000 });
|
||||
|
||||
// Test API key generation in JSON mode
|
||||
const generateApiKeyButton = page.getByText("Generate API key");
|
||||
const isGenerateButtonVisible = await generateApiKeyButton
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
if (isGenerateButtonVisible) {
|
||||
// Get the JSON configuration before generating
|
||||
const preElement = page.locator("pre").first();
|
||||
const jsonBeforeGeneration = await preElement.textContent();
|
||||
|
||||
// Verify "YOUR_API_KEY" is present in the JSON before generation
|
||||
expect(jsonBeforeGeneration).toContain("YOUR_API_KEY");
|
||||
|
||||
// Verify the button is visible and clickable
|
||||
await expect(generateApiKeyButton).toBeVisible();
|
||||
await expect(generateApiKeyButton).toBeEnabled();
|
||||
|
||||
// Click the Generate API key button
|
||||
await generateApiKeyButton.click();
|
||||
|
||||
// Wait for the API key to be generated and verify the state change
|
||||
await expect(page.getByText("API key generated")).toBeVisible({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
// Wait for the JSON to update - it should no longer contain "YOUR_API_KEY"
|
||||
await expect(preElement).not.toContainText("YOUR_API_KEY", {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const jsonAfterGeneration = await preElement.textContent();
|
||||
|
||||
// Verify that an actual API key (not "YOUR_API_KEY") is present
|
||||
const apiKeyMatch = jsonAfterGeneration?.match(
|
||||
/"x-api-key"[\s,]*"([^"]+)"/,
|
||||
);
|
||||
expect(apiKeyMatch).not.toBeNull();
|
||||
if (apiKeyMatch) {
|
||||
const generatedApiKey = apiKeyMatch[1];
|
||||
expect(generatedApiKey).not.toBe("YOUR_API_KEY");
|
||||
expect(generatedApiKey.length).toBeGreaterThan(0);
|
||||
expect(generatedApiKey.trim().length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
// Verify the Generate API key button text is no longer visible
|
||||
await expect(generateApiKeyButton).not.toBeVisible();
|
||||
} else {
|
||||
// If button is not visible, verify we're in a valid state
|
||||
const apiKeyGeneratedText = page.getByText("API key generated");
|
||||
const hasApiKeyGenerated = await apiKeyGeneratedText
|
||||
.isVisible()
|
||||
.catch(() => false);
|
||||
|
||||
expect(
|
||||
hasApiKeyGenerated ||
|
||||
!(await page.getByText("Generate API key").isVisible()),
|
||||
).toBeTruthy();
|
||||
}
|
||||
|
||||
// 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.evaluate(() => {
|
||||
return navigator.clipboard.readText();
|
||||
});
|
||||
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*"\/c"\s*,\s*"uvx"\s*,\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatch).not.toBeNull();
|
||||
|
||||
await page.getByText("macOS/Linux", { exact: true }).click();
|
||||
|
||||
await page.waitForSelector("pre", { state: "visible", timeout: 30000 });
|
||||
// Copy configuration
|
||||
await page.getByTestId("icon-copy").click();
|
||||
await expect(page.getByTestId("icon-check")).toBeVisible();
|
||||
|
||||
const configJsonLinux = await page.evaluate(() => {
|
||||
return navigator.clipboard.readText();
|
||||
});
|
||||
|
||||
const sseUrlMatchLinux = configJsonLinux?.match(
|
||||
/"args":\s*\[\s*"mcp-proxy"\s*,\s*"([^"]+)"/,
|
||||
);
|
||||
expect(sseUrlMatchLinux).not.toBeNull();
|
||||
|
||||
// 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");
|
||||
|
||||
await page.waitForSelector('[data-testid="models_and_agentsMCP Tools"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId("models_and_agentsMCP Tools")
|
||||
.dragTo(page.locator('//*[@id="react-flow-id"]'), {
|
||||
targetPosition: { x: 50, y: 50 },
|
||||
});
|
||||
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="json-input"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
const randomSuffix = Math.floor(Math.random() * 90000) + 10000;
|
||||
const testName = `test_server_${randomSuffix}`;
|
||||
|
||||
await page
|
||||
.getByTestId("json-input")
|
||||
.fill(configJsonLinux.replace(/lf-starter_project/g, testName) || "");
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 30000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
// Verify that tools are available in the dropdown
|
||||
// The dropdown should show tool options (the action_name rename may not appear here)
|
||||
const toolOptions = page.locator('[data-testid*="-option"]');
|
||||
const toolCount = await toolOptions.count();
|
||||
|
||||
expect(toolCount).toBeGreaterThan(0);
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { adjustScreenView } from "../../utils/adjust-screen-view";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
import { openAddMcpServerModal } from "../../utils/open-add-mcp-server-modal";
|
||||
import { zoomOut } from "../../utils/zoom-out";
|
||||
|
||||
test(
|
||||
"user must be able to change mode of MCP tools without any issues",
|
||||
{ tag: ["@release", "@workspace", "@components"] },
|
||||
async ({ page }) => {
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||
@ -48,21 +50,7 @@ test(
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
await page.getByTestId("stdio-tab").click();
|
||||
|
||||
@ -91,8 +79,6 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByText("Select a tool");
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
const fetchOptionCount = await page.getByTestId("fetch-0-option").count();
|
||||
@ -101,7 +87,6 @@ test(
|
||||
|
||||
await page.getByTestId("fetch-0-option").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
await adjustScreenView(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="int_int_max_length"]', {
|
||||
@ -125,6 +110,8 @@ test(
|
||||
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 3000 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.waitForSelector('[data-testid="sidebar-nav-MCP Servers"]', {
|
||||
timeout: 30000,
|
||||
});
|
||||
@ -148,6 +135,8 @@ test(
|
||||
.first()
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
@ -169,12 +158,16 @@ test(
|
||||
"uvx mcp-server-fetch",
|
||||
);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await page
|
||||
.getByTestId(`mcp-server-menu-button-${testName}`)
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page
|
||||
.getByText("Delete", { exact: true })
|
||||
.first()
|
||||
@ -195,35 +188,7 @@ test(
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await expect(page.getByText(testName)).not.toBeVisible({
|
||||
timeout: 3000,
|
||||
});
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
const newFlowDiv = await page
|
||||
.getByTestId("flow-name-div")
|
||||
.filter({ hasText: "New Flow" })
|
||||
.first();
|
||||
await newFlowDiv.click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.waitForSelector('[data-testid="save-mcp-server-button"]', {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await page.getByTestId("save-mcp-server-button").click({ timeout: 10000 });
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(page.getByTestId("save-mcp-server-button")).toBeHidden({
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 10000 });
|
||||
await expect(page.getByText(testName)).toHaveCount(2, {
|
||||
timeout: 10000,
|
||||
});
|
||||
},
|
||||
@ -241,10 +206,12 @@ test(
|
||||
await page.getByTestId("blank-flow").click();
|
||||
await page.getByTestId("sidebar-nav-mcp").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const sidebarButton = page.getByTestId("sidebar-add-mcp-server-button");
|
||||
const fallbackButton = page.getByTestId("add-mcp-server-button-sidebar");
|
||||
|
||||
if (await sidebarButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
if (await sidebarButton.isVisible({ timeout: 30000 }).catch(() => false)) {
|
||||
await sidebarButton.click();
|
||||
} else {
|
||||
await fallbackButton.click();
|
||||
@ -265,14 +232,18 @@ test(
|
||||
const testName = `test_server_${randomSuffix}`;
|
||||
await page.getByTestId("stdio-name-input").fill(testName);
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId("stdio-command-input").fill("uvx mcp-server-fetch");
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.getByTestId(`add-component-button-${testName}`).click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
@ -285,8 +256,6 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByText("Select a tool");
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
const fetchOptionCount = await page.getByTestId("fetch-0-option").count();
|
||||
@ -295,7 +264,11 @@ test(
|
||||
|
||||
await page.getByTestId("fetch-0-option").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
// Wait for canvas controls to be visible before adjusting view
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
state: "visible",
|
||||
timeout: 10000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
@ -343,10 +316,8 @@ test(
|
||||
|
||||
await page.getByTestId("save-mcp-server-button").click({ timeout: 10000 });
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await expect(page.getByTestId("save-mcp-server-button")).toBeHidden({
|
||||
timeout: 10000,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 10000 });
|
||||
@ -380,21 +351,7 @@ test(
|
||||
});
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
// Go to STDIO tab and fill all fields
|
||||
await page.getByTestId("stdio-tab").click();
|
||||
@ -442,12 +399,9 @@ test(
|
||||
// Save the server
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
// Wait for server to be created
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Go to settings to edit the server
|
||||
await page.getByTestId("user_menu_button").click({ timeout: 3000 });
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 3000 });
|
||||
await page.getByTestId("user_menu_button").click({ timeout: 10000 });
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 10000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="sidebar-nav-MCP Servers"]', {
|
||||
timeout: 30000,
|
||||
@ -550,21 +504,7 @@ test(
|
||||
});
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
// Go to HTTP tab and fill all fields
|
||||
await page.getByTestId("http-tab").click();
|
||||
@ -611,12 +551,9 @@ test(
|
||||
// Save the server
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
// Wait for server to be created
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Go to settings to edit the server
|
||||
await page.getByTestId("user_menu_button").click({ timeout: 3000 });
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 3000 });
|
||||
await page.getByTestId("user_menu_button").click({ timeout: 10000 });
|
||||
await page.getByTestId("menu_settings_button").click({ timeout: 10000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="sidebar-nav-MCP Servers"]', {
|
||||
timeout: 30000,
|
||||
@ -706,6 +643,8 @@ test(
|
||||
"mcp server tools should be refreshed when editing a server",
|
||||
{ tag: ["@release", "@workspace", "@components"] },
|
||||
async ({ page }) => {
|
||||
await page.waitForTimeout(10000);
|
||||
|
||||
await awaitBootstrapTest(page);
|
||||
|
||||
await page.waitForSelector('[data-testid="blank-flow"]', {
|
||||
@ -734,21 +673,7 @@ test(
|
||||
|
||||
await expect(page.getByTestId("dropdown_str_tool")).toBeHidden();
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
await page.getByTestId("stdio-tab").click();
|
||||
|
||||
@ -765,6 +690,8 @@ test(
|
||||
|
||||
await page.getByTestId("add-mcp-server-button").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
@ -773,17 +700,21 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByText("Select a tool");
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const fetchOptionCount = await page.getByTestId("fetch-0-option").count();
|
||||
|
||||
expect(fetchOptionCount).toBeGreaterThan(0);
|
||||
|
||||
await page.getByTestId("fetch-0-option").click();
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
// Wait for canvas controls to be visible before adjusting view
|
||||
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
|
||||
state: "visible",
|
||||
timeout: 10000,
|
||||
});
|
||||
await page.getByTestId("canvas_controls_dropdown").click();
|
||||
|
||||
await page.getByTestId("fit_view").click();
|
||||
@ -860,25 +791,24 @@ test(
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
const newFlowDiv = await page
|
||||
const newFlowDiv = page
|
||||
.getByTestId("flow-name-div")
|
||||
.filter({ hasText: "New Flow" })
|
||||
.first();
|
||||
await newFlowDiv.click();
|
||||
|
||||
try {
|
||||
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
});
|
||||
} catch (_) {
|
||||
console.warn("Dropdown tool is not disabled, continuing...");
|
||||
}
|
||||
// Re-select the server after returning to flow (server reference may be lost after editing)
|
||||
await page.waitForSelector('[data-testid="mcp-server-dropdown"]', {
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
});
|
||||
await page.getByTestId("mcp-server-dropdown").click();
|
||||
await page.getByTestId(`list_item_${testName}`).click({ timeout: 5000 });
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
timeout: 30000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
@ -925,13 +855,11 @@ test(
|
||||
.click({ timeout: 3000 });
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button-page"]', {
|
||||
timeout: 3000,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await expect(page.getByText(testName)).not.toBeVisible({
|
||||
timeout: 3000,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
await page.getByTestId("add-mcp-server-button-page").click();
|
||||
@ -960,31 +888,28 @@ test(
|
||||
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
|
||||
const newFlowDiv2 = await page
|
||||
const newFlowDiv2 = page
|
||||
.getByTestId("flow-name-div")
|
||||
.filter({ hasText: "New Flow" })
|
||||
.first();
|
||||
await newFlowDiv2.click();
|
||||
|
||||
try {
|
||||
await page.waitForSelector('[data-testid="dropdown_str_tool"]:disabled', {
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
});
|
||||
} catch (_) {
|
||||
console.warn("Dropdown tool is not disabled, continuing...");
|
||||
}
|
||||
// Re-select the server after returning to flow (server reference may be lost after editing)
|
||||
await page.waitForSelector('[data-testid="mcp-server-dropdown"]', {
|
||||
timeout: 10000,
|
||||
state: "visible",
|
||||
});
|
||||
await page.getByTestId("mcp-server-dropdown").click();
|
||||
await page.getByTestId(`list_item_${testName}`).click({ timeout: 5000 });
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="dropdown_str_tool"]:not([disabled])',
|
||||
{
|
||||
timeout: 10000,
|
||||
timeout: 30000,
|
||||
state: "visible",
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByText("Select a tool");
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
const fetchOptionCount2 = await page.getByTestId("fetch-0-option").count();
|
||||
@ -1020,21 +945,7 @@ test(
|
||||
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
// Switch to HTTP tab for Streamable HTTP
|
||||
await page.getByTestId("http-tab").click();
|
||||
@ -1102,6 +1013,8 @@ test(
|
||||
"SSE MCP server with deepwiki should load tools correctly",
|
||||
{ tag: ["@release", "@workspace", "@components"] },
|
||||
async ({ page }) => {
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Start the MCP server with proper health checking
|
||||
const server = "https://mcp.deepwiki.com/sse";
|
||||
|
||||
@ -1126,21 +1039,7 @@ test(
|
||||
|
||||
await adjustScreenView(page, { numberOfZoomOut: 3 });
|
||||
|
||||
try {
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch (_error) {
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
await openAddMcpServerModal(page);
|
||||
|
||||
// Switch to HTTP tab for SSE
|
||||
await page.getByTestId("http-tab").click();
|
||||
@ -1170,8 +1069,6 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByText("Select a tool");
|
||||
|
||||
await page.getByTestId("dropdown_str_tool").click();
|
||||
|
||||
// Check for tools from wiki
|
||||
|
||||
20
src/frontend/tests/utils/open-add-mcp-server-modal.ts
Normal file
20
src/frontend/tests/utils/open-add-mcp-server-modal.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
export async function openAddMcpServerModal(page: Page) {
|
||||
// Try the simple button first (when no servers exist)
|
||||
const simpleButton = page.getByTestId("add-mcp-server-simple-button");
|
||||
if (await simpleButton.isVisible({ timeout: 1000 }).catch(() => false)) {
|
||||
await simpleButton.click();
|
||||
} else {
|
||||
// Otherwise use the dropdown
|
||||
await page.getByTestId("mcp-server-dropdown").click({ timeout: 3000 });
|
||||
await page.getByText("Add MCP Server", { exact: true }).click({
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
await page.waitForSelector('[data-testid="add-mcp-server-button"]', {
|
||||
state: "visible",
|
||||
timeout: 30000,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user