diff --git a/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts b/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts index 9215390ef9..fbaaed2719 100644 --- a/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts +++ b/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts @@ -1,7 +1,8 @@ -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "select and delete a flow", { tag: ["@release", "@mainpage"] }, @@ -9,7 +10,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, @@ -26,15 +29,17 @@ test( timeout: 1000, }); // click on the delete button - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); await page.getByText("This can't be undone.").isVisible({ timeout: 1000, }); //confirm the deletion in the modal - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); - await page.getByText("Selected items deleted successfully").isVisible(); + await expect( + page.getByText("Selected items deleted successfully"), + ).toBeVisible(); }, ); @@ -42,7 +47,9 @@ test("search flows", { tag: ["@release", "@mainpage"] }, async ({ page }) => { await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, @@ -50,7 +57,7 @@ test("search flows", { tag: ["@release", "@mainpage"] }, async ({ page }) => { await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByText("New Flow").isVisible(); + await expect(page.getByText("New Flow")).toBeVisible(); await page.getByTestId("new-project-btn").click(); await page.getByTestId("side_nav_options_all-templates").click(); await page.getByRole("heading", { name: "Memory Chatbot" }).click(); @@ -70,9 +77,11 @@ test("search flows", { tag: ["@release", "@mainpage"] }, async ({ page }) => { await page.getByTestId("icon-ChevronLeft").first().click(); await page.getByPlaceholder("Search flows").fill("Memory Chatbot"); - await page.getByText("Memory Chatbot", { exact: true }).isVisible(); + await expect(page.getByText("Memory Chatbot", { exact: true })).toBeVisible(); await page.getByText("Document Q&A", { exact: true }).isHidden(); - await page.getByText("Basic Prompting", { exact: true }).isHidden(); + await page + .getByText(TEXTS.templateBasicPrompting, { exact: true }) + .isHidden(); }); test( @@ -83,11 +92,13 @@ test( if (await page.getByTestId("components-btn").isVisible()) { await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page, { numberOfZoomOut: 2 }); - await page.getByText("Chat Input").first().click(); + await page.getByText(TEXTS.componentChatInput).first().click(); await page.waitForSelector('[data-testid="more-options-modal"]', { timeout: 1000, }); @@ -123,15 +134,19 @@ test( await page.getByTestId("icon-ChevronLeft").first().click(); - const exitButton = await page.getByText("Exit", { exact: true }).count(); + const exitButton = await page + .getByText(TEXTS.exit, { exact: true }) + .count(); if (exitButton > 0) { - await page.getByText("Exit", { exact: true }).click(); + await page.getByText(TEXTS.exit, { exact: true }).click(); } await page.getByTestId("components-btn").click(); await page.getByPlaceholder("Search components").fill("Chat Input"); - await page.getByText("Chat Input", { exact: true }).isVisible(); + await expect( + page.getByText(TEXTS.componentChatInput, { exact: true }), + ).toBeVisible(); await page.getByText("Prompt", { exact: true }).isHidden(); await page.getByText("OpenAI", { exact: true }).isHidden(); } diff --git a/src/frontend/tests/core/features/auto-login-off.spec.ts b/src/frontend/tests/core/features/auto-login-off.spec.ts index 57afc42ffd..1a32327b09 100644 --- a/src/frontend/tests/core/features/auto-login-off.spec.ts +++ b/src/frontend/tests/core/features/auto-login-off.spec.ts @@ -5,6 +5,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "when auto_login is false, admin can CRUD user's and should see just your own flows", { tag: ["@release", "@api", "@database", "@mainpage"] }, @@ -41,16 +42,22 @@ test( await page.goto("/"); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); await page.waitForSelector('[data-testid="mainpage_title"]', { timeout: 30000, @@ -67,7 +74,10 @@ test( //CRUD an user await page.getByText("New User", { exact: true }).click(); - await page.getByPlaceholder("Username").last().fill(randomName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(randomName); await page.locator('input[name="password"]').fill(randomPassword); await page.locator('input[name="confirmpassword"]').fill(randomPassword); @@ -77,7 +87,7 @@ test( await page.locator("#is_active").click(); - await page.getByText("Save", { exact: true }).click(); + await page.getByText(TEXTS.save, { exact: true }).click(); await page.waitForSelector("text=new user added", { timeout: 30000 }); @@ -86,7 +96,7 @@ test( }); await page.getByTestId("icon-Trash2").last().click(); - await page.getByText("Delete", { exact: true }).last().click(); + await page.getByText(TEXTS.delete, { exact: true }).last().click(); await page.waitForSelector("text=user deleted", { timeout: 30000 }); @@ -97,7 +107,10 @@ test( await page.getByText("New User", { exact: true }).click(); - await page.getByPlaceholder("Username").last().fill(randomName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(randomName); await page.locator('input[name="password"]').fill(randomPassword); await page.locator('input[name="confirmpassword"]').fill(randomPassword); @@ -107,7 +120,7 @@ test( await page.locator("#is_active").click(); - await page.getByText("Save", { exact: true }).click(); + await page.getByText(TEXTS.save, { exact: true }).click(); await page.waitForSelector("text=new user added", { timeout: 30000 }); @@ -115,14 +128,20 @@ test( (response) => response.url().includes("/api/v1/users") && response.status() === 200, ); - await page.getByPlaceholder("Username").last().fill(randomName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(randomName); await searchResponse; await page.getByTestId("icon-Pencil").last().click(); - await page.getByPlaceholder("Username").last().fill(secondRandomName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(secondRandomName); - await page.getByText("Save", { exact: true }).click(); + await page.getByText(TEXTS.save, { exact: true }).click(); await page.waitForSelector("text=user edited", { timeout: 30000 }); @@ -146,7 +165,9 @@ test( await awaitBootstrapTest(page, { skipGoto: true }); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page, { numberOfZoomOut: 1 }); @@ -182,18 +203,22 @@ test( sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill(secondRandomName); - await page.getByPlaceholder("Password").fill(randomPassword); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(secondRandomName); + await page.getByPlaceholder(TEXTS.placeholderPassword).fill(randomPassword); await page.waitForSelector("text=Sign in", { timeout: 1500, }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); @@ -216,7 +241,9 @@ test( await awaitBootstrapTest(page, { skipGoto: true }); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page, { numberOfZoomOut: 2 }); @@ -249,18 +276,24 @@ test( sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); await page.waitForSelector('[data-testid="mainpage_title"]', { timeout: 30000, diff --git a/src/frontend/tests/core/features/bulk-delete-sessions.spec.ts b/src/frontend/tests/core/features/bulk-delete-sessions.spec.ts index e2832f46ca..fd4804934e 100644 --- a/src/frontend/tests/core/features/bulk-delete-sessions.spec.ts +++ b/src/frontend/tests/core/features/bulk-delete-sessions.spec.ts @@ -3,6 +3,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import type { Page } from "@playwright/test"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("Bulk Delete Sessions", () => { // Helper to send a message in the playground async function sendMessage(page: Page, message: string) { @@ -73,12 +74,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -107,12 +110,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -156,12 +161,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -206,12 +213,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -266,12 +275,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -322,12 +333,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -383,12 +396,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -420,12 +435,14 @@ test.describe("Bulk Delete Sessions", () => { // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/core/features/chatInputOutputUser-shard-0.spec.ts b/src/frontend/tests/core/features/chatInputOutputUser-shard-0.spec.ts index 6445622ae8..8cfe2f6e90 100644 --- a/src/frontend/tests/core/features/chatInputOutputUser-shard-0.spec.ts +++ b/src/frontend/tests/core/features/chatInputOutputUser-shard-0.spec.ts @@ -8,6 +8,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to send an image on chat", { tag: ["@release", "@workspace", "@components"] }, @@ -24,7 +25,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); @@ -33,10 +36,12 @@ test( await page.waitForSelector("text=Chat Input", { timeout: 30000 }); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await openAdvancedOptions(page); await closeAdvancedOptions(page); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, diff --git a/src/frontend/tests/core/features/componentHoverAdd.spec.ts b/src/frontend/tests/core/features/componentHoverAdd.spec.ts index a975b40919..692d82e692 100644 --- a/src/frontend/tests/core/features/componentHoverAdd.spec.ts +++ b/src/frontend/tests/core/features/componentHoverAdd.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user can add components by hovering and clicking the plus icon", { tag: ["@release", "@components", "@workspace"] }, @@ -17,7 +18,7 @@ test( // Search for a component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatInput); await page.waitForSelector('[data-testid="input_outputChat Input"]', { timeout: 2000, diff --git a/src/frontend/tests/core/features/composio.spec.ts b/src/frontend/tests/core/features/composio.spec.ts index 3d171d02d8..35792a9b2e 100644 --- a/src/frontend/tests/core/features/composio.spec.ts +++ b/src/frontend/tests/core/features/composio.spec.ts @@ -3,6 +3,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { removeOldApiKeys } from "../../utils/remove-old-api-keys"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to interact with composio component", { tag: ["@release", "@workspace", "@api", "@components"] }, @@ -59,7 +60,7 @@ test( await page.getByTestId("button_run_gmail").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000, }); diff --git a/src/frontend/tests/core/features/customComponentAdd.spec.ts b/src/frontend/tests/core/features/customComponentAdd.spec.ts index 604a5d1e38..201be4f303 100644 --- a/src/frontend/tests/core/features/customComponentAdd.spec.ts +++ b/src/frontend/tests/core/features/customComponentAdd.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "custom component code button should be pink when adding custom component", { tag: ["@release", "@components"] }, @@ -63,7 +64,7 @@ class CustomComponent(Component): await page.keyboard.press(`ControlOrMeta+A`); await page.locator("textarea").last().fill(waitTimeoutCode); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); await expect(page.getByTestId("code-button-modal").last()).not.toHaveClass( /animate-pulse-pink/, diff --git a/src/frontend/tests/core/features/default-system-prompt.spec.ts b/src/frontend/tests/core/features/default-system-prompt.spec.ts index bbd72f19ba..d36f4ccc23 100644 --- a/src/frontend/tests/core/features/default-system-prompt.spec.ts +++ b/src/frontend/tests/core/features/default-system-prompt.spec.ts @@ -18,6 +18,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; // Every section header the new default template must contain, in order. const SECTION_HEADERS = [ "# Identity", @@ -158,7 +159,10 @@ test( // Load Simple Agent — gives us a ready-to-run Agent wired to ChatInput/Output. await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); // Simple Agent's template may carry its own system_prompt — force the fresh @@ -181,7 +185,7 @@ test( await page.getByTestId("input-chat-playground").last().fill("Run now."); await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 30000 }); await stopButton.waitFor({ state: "hidden", timeout: 120000 }); @@ -220,7 +224,10 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); const customPrompt = @@ -238,7 +245,7 @@ test( .fill("Introduce yourself in one short sentence."); await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 30000 }); await stopButton.waitFor({ state: "hidden", timeout: 120000 }); @@ -280,7 +287,10 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); // Force the model to echo the date from its context — any obedience-style @@ -301,7 +311,7 @@ test( .fill("What is today's date?"); await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 30000 }); await stopButton.waitFor({ state: "hidden", timeout: 120000 }); diff --git a/src/frontend/tests/core/features/deployment-create.spec.ts b/src/frontend/tests/core/features/deployment-create.spec.ts index 4367f386dc..808282a329 100644 --- a/src/frontend/tests/core/features/deployment-create.spec.ts +++ b/src/frontend/tests/core/features/deployment-create.spec.ts @@ -12,6 +12,11 @@ import { SNAPSHOTS_EMPTY_MOCK, } from "../../utils/deployment-mocks"; +test.skip( + process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", +); + // --------------------------------------------------------------------------- // Helper: set up all required API route mocks // --------------------------------------------------------------------------- @@ -225,11 +230,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await awaitBootstrapTest(page, { skipModal: true }); await setupDeploymentMocks(page, ""); await page.getByTestId("deployments-btn").click(); @@ -250,11 +250,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); // Next should be disabled before selection @@ -277,11 +272,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); await selectProvider(page); await page.getByTestId("deployment-stepper-next").click(); @@ -318,11 +308,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); await goToStepType(page); @@ -367,11 +352,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); await goToStepReview(page); @@ -399,11 +379,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); // Set up POST deployments mock (after bootstrap, before deploy click) @@ -454,11 +429,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page); await goToStepReview(page); @@ -493,11 +463,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page, SNAPSHOTS_DUPLICATE_MOCK); await goToStepReview(page); @@ -520,11 +485,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page, SNAPSHOTS_EMPTY_MOCK); await goToStepReview(page); @@ -547,11 +507,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page, SNAPSHOTS_DUPLICATE_MOCK); await goToStepReview(page); @@ -590,11 +545,6 @@ test( tag: ["@deployment", "@workspace"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await openDeploymentStepper(page, SNAPSHOTS_EMPTY_MOCK, [ { ...FLOWS_MOCK[0], name: "12925" }, ]); diff --git a/src/frontend/tests/core/features/deployment-edit.spec.ts b/src/frontend/tests/core/features/deployment-edit.spec.ts index ba8062ccae..f8bb6a7cf0 100644 --- a/src/frontend/tests/core/features/deployment-edit.spec.ts +++ b/src/frontend/tests/core/features/deployment-edit.spec.ts @@ -12,6 +12,11 @@ import { PROVIDERS_MOCK, } from "../../utils/deployment-mocks"; +test.skip( + process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", +); + async function setupRoutes(page: Parameters[2]["page"]) { // Register broad catch-all FIRST so specific routes (registered after) take priority via LIFO await page.route("**/api/v1/deployments*", (route) => { @@ -101,11 +106,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupRoutes(page); await navigateToDeployments(page); @@ -128,11 +128,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupRoutes(page); await navigateToDeployments(page); @@ -161,11 +156,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupRoutes(page); await navigateToDeployments(page); @@ -188,11 +178,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupRoutes(page); await navigateToDeployments(page); @@ -233,11 +218,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupRoutes(page); await navigateToDeployments(page); @@ -382,11 +362,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - // Capture folder ID from the projects API before bootstrap const projectsResponsePromise = page.waitForResponse( (resp) => @@ -483,11 +458,6 @@ test( tag: ["@release", "@workspace", "@api"], }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - const projectsResponsePromise = page.waitForResponse( (resp) => resp.url().includes("/api/v1/projects") && resp.status() === 200, diff --git a/src/frontend/tests/core/features/deployment-providers.spec.ts b/src/frontend/tests/core/features/deployment-providers.spec.ts index 44d6037975..b9b83afd6a 100644 --- a/src/frontend/tests/core/features/deployment-providers.spec.ts +++ b/src/frontend/tests/core/features/deployment-providers.spec.ts @@ -6,6 +6,11 @@ import { PROVIDERS_MOCK, } from "../../utils/deployment-mocks"; +test.skip( + process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", +); + async function navigateToProvidersTab( page: Parameters[2]["page"], ) { @@ -19,11 +24,6 @@ test( "Empty state shows Add Environment button", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -50,11 +50,6 @@ test( "New Environment button opens modal with save disabled on empty form", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -84,11 +79,6 @@ test( "Save button becomes enabled after filling all required fields", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -127,11 +117,6 @@ test( "Save calls POST and modal closes", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let postCalled = false; await page.route("**/api/v1/deployments/providers*", (route) => { @@ -196,11 +181,6 @@ test( "Delete triggers confirmation dialog then calls DELETE", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments*", (route) => { route.fulfill({ status: 200, @@ -256,11 +236,6 @@ test( "Cancel delete dismisses confirmation without calling DELETE", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let deleteRequestCount = 0; await page.route("**/api/v1/deployments*", (route) => { diff --git a/src/frontend/tests/core/features/deployment-test-modal.spec.ts b/src/frontend/tests/core/features/deployment-test-modal.spec.ts index 8a36ca7712..49fc2a4262 100644 --- a/src/frontend/tests/core/features/deployment-test-modal.spec.ts +++ b/src/frontend/tests/core/features/deployment-test-modal.spec.ts @@ -9,6 +9,12 @@ import { RUNNING_RUN_RESPONSE, } from "../../utils/deployment-mocks"; +import { TEXTS } from "../../utils/constants/texts"; +test.skip( + process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", +); + async function setupBaseRoutes(page: Page) { // Register broad catch-all FIRST so specific routes (registered after) take priority via LIFO await page.route("**/api/v1/deployments*", (route) => { @@ -44,18 +50,13 @@ test( "Test button opens modal with chat interface", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await setupBaseRoutes(page); await navigateToDeploymentsPage(page); await page.getByTestId("test-deployment-dep-1").click(); await expect(page.getByTestId("test-deployment-modal-title")).toBeVisible(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); }, ); @@ -63,11 +64,6 @@ test( "Send message calls POST run with correct body", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let capturedRequestBody: Record | null = null; let runCallCount = 0; @@ -102,9 +98,9 @@ test( await navigateToDeploymentsPage(page); await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); - await page.getByPlaceholder("Message").fill("Hello AI"); + await page.getByPlaceholder(TEXTS.placeholderMessage).fill("Hello AI"); await page.getByRole("button", { name: /send message/i }).click(); await expect @@ -119,11 +115,6 @@ test( "Response appears in chat after polling completes", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let runCallCount = 0; await setupBaseRoutes(page); @@ -155,12 +146,12 @@ test( await navigateToDeploymentsPage(page); await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); - await page.getByPlaceholder("Message").fill("Hello AI"); + await page.getByPlaceholder(TEXTS.placeholderMessage).fill("Hello AI"); await page.getByRole("button", { name: /send message/i }).click(); - await expect(page.getByText("Hello from AI")).toBeVisible({ + await expect(page.getByText(TEXTS.labelHelloFromAi)).toBeVisible({ timeout: 30_000, }); expect(runCallCount).toBeGreaterThanOrEqual(2); @@ -171,11 +162,6 @@ test( "Input is disabled during polling and re-enabled after response", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let runCallCount = 0; await setupBaseRoutes(page); @@ -207,19 +193,21 @@ test( await navigateToDeploymentsPage(page); await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); - await page.getByPlaceholder("Message").fill("Hello AI"); + await page.getByPlaceholder(TEXTS.placeholderMessage).fill("Hello AI"); await page.getByRole("button", { name: /send message/i }).click(); // Textarea should be disabled while waiting for response - await expect(page.getByPlaceholder("Message")).toBeDisabled(); + await expect( + page.getByPlaceholder(TEXTS.placeholderMessage), + ).toBeDisabled(); // After response arrives, textarea should be re-enabled - await expect(page.getByText("Hello from AI")).toBeVisible({ + await expect(page.getByText(TEXTS.labelHelloFromAi)).toBeVisible({ timeout: 30_000, }); - await expect(page.getByPlaceholder("Message")).toBeEnabled(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeEnabled(); }, ); @@ -227,11 +215,6 @@ test( "Multi-turn: second message includes thread_id from first response", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let runCallCount = 0; const capturedBodies: Array> = []; @@ -268,23 +251,25 @@ test( await navigateToDeploymentsPage(page); await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); // First message - await page.getByPlaceholder("Message").fill("First message"); + await page.getByPlaceholder(TEXTS.placeholderMessage).fill("First message"); await page.getByRole("button", { name: /send message/i }).click(); // Wait for first response - await expect(page.getByText("Hello from AI")).toBeVisible({ + await expect(page.getByText(TEXTS.labelHelloFromAi)).toBeVisible({ timeout: 30_000, }); // Second message - await page.getByPlaceholder("Message").fill("Second message"); + await page + .getByPlaceholder(TEXTS.placeholderMessage) + .fill("Second message"); await page.getByRole("button", { name: /send message/i }).click(); // Wait for second response - await expect(page.getByText("Hello from AI")).toHaveCount(2, { + await expect(page.getByText(TEXTS.labelHelloFromAi)).toHaveCount(2, { timeout: 30_000, }); @@ -303,11 +288,6 @@ test( "Close and reopen modal resets chat history", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let runCallCount = 0; await setupBaseRoutes(page); @@ -340,13 +320,13 @@ test( // Open modal and send a message await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); - await page.getByPlaceholder("Message").fill("Hello AI"); + await page.getByPlaceholder(TEXTS.placeholderMessage).fill("Hello AI"); await page.getByRole("button", { name: /send message/i }).click(); // Wait for response to appear - await expect(page.getByText("Hello from AI")).toBeVisible({ + await expect(page.getByText(TEXTS.labelHelloFromAi)).toBeVisible({ timeout: 30_000, }); @@ -354,16 +334,18 @@ test( await page.keyboard.press("Escape"); // Verify the modal is closed - await expect(page.getByPlaceholder("Message")).not.toBeVisible(); + await expect( + page.getByPlaceholder(TEXTS.placeholderMessage), + ).not.toBeVisible(); // Reopen the modal runCallCount = 0; await page.getByTestId("test-deployment-dep-1").click(); - await expect(page.getByPlaceholder("Message")).toBeVisible(); + await expect(page.getByPlaceholder(TEXTS.placeholderMessage)).toBeVisible(); // Chat should be empty — no previous messages visible await expect(page.getByText("Hello AI")).not.toBeVisible(); - await expect(page.getByText("Hello from AI")).not.toBeVisible(); + await expect(page.getByText(TEXTS.labelHelloFromAi)).not.toBeVisible(); // Empty state ("Agent Chat") should be shown await expect(page.getByText("Agent Chat")).toBeVisible(); diff --git a/src/frontend/tests/core/features/deployments-page.spec.ts b/src/frontend/tests/core/features/deployments-page.spec.ts index d06d0956d7..80bd5ce91f 100644 --- a/src/frontend/tests/core/features/deployments-page.spec.ts +++ b/src/frontend/tests/core/features/deployments-page.spec.ts @@ -6,6 +6,11 @@ import { PROVIDERS_MOCK, } from "../../utils/deployment-mocks"; +test.skip( + process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", +); + async function navigateToDeploymentsTab( page: Parameters[2]["page"], ) { @@ -18,11 +23,6 @@ test( "Renders Deployments tab with sub-tab toggles", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -52,11 +52,6 @@ test( "Deployments empty state shows create button when no providers exist", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -85,11 +80,6 @@ test( "Providers empty state shows add provider button when switching to providers tab", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -120,11 +110,6 @@ test( "Deployment list renders a row for each deployment", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments*", (route) => { route.fulfill({ status: 200, @@ -153,11 +138,6 @@ test( "Provider list renders a row for each provider", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments*", (route) => { route.fulfill({ status: 200, @@ -188,11 +168,6 @@ test( "Delete deployment opens type-to-confirm dialog", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -225,11 +200,6 @@ test( "Delete deployment button is disabled until deployment name is typed", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -267,11 +237,6 @@ test( "Delete deployment — typing correct name and confirming calls DELETE", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - await page.route("**/api/v1/deployments/providers*", (route) => { route.fulfill({ status: 200, @@ -320,11 +285,6 @@ test( "Cancel delete deployment dismisses dialog without calling DELETE", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let deleteRequestCount = 0; await page.route("**/api/v1/deployments/providers*", (route) => { @@ -379,11 +339,6 @@ test( "Names filter is passed to API when fetching deployments", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - process.env.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", - "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", - ); - let resolveNamesRequest: ((url: string) => void) | undefined; const namesRequest = new Promise((resolve) => { resolveNamesRequest = resolve; diff --git a/src/frontend/tests/core/features/flow-lock.spec.ts b/src/frontend/tests/core/features/flow-lock.spec.ts index 3f4ffb3723..1b219515fe 100644 --- a/src/frontend/tests/core/features/flow-lock.spec.ts +++ b/src/frontend/tests/core/features/flow-lock.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("Flow Lock Feature", () => { test( "should lock and unlock a flow and verify UI changes", @@ -10,7 +11,9 @@ test.describe("Flow Lock Feature", () => { // Navigate to templates and select a flow to work with await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 5000, @@ -120,7 +123,9 @@ test.describe("Flow Lock Feature", () => { // Navigate to templates and select a flow await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 5000, diff --git a/src/frontend/tests/core/features/folder-deletion-integrity.spec.ts b/src/frontend/tests/core/features/folder-deletion-integrity.spec.ts index 2d9f8c94a5..b4fc737f50 100644 --- a/src/frontend/tests/core/features/folder-deletion-integrity.spec.ts +++ b/src/frontend/tests/core/features/folder-deletion-integrity.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; /** * Tests for folder deletion integrity * @@ -18,7 +19,9 @@ test( // Navigate to templates and create a flow first await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 30000, @@ -36,14 +39,14 @@ test( await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); // Rename the folder for easier identification await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -71,10 +74,10 @@ test( .waitFor({ state: "visible", timeout: 5000 }); await page.getByTestId("more-options-button_test-folder-to-delete").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); // Verify success message - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); @@ -98,7 +101,9 @@ test( // Navigate to templates await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 30000, @@ -116,13 +121,13 @@ test( await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -141,13 +146,13 @@ test( await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -177,10 +182,10 @@ test( .waitFor({ state: "visible", timeout: 5000 }); await page.getByTestId("more-options-button_folder-alpha").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); // Verify success message - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); @@ -208,9 +213,9 @@ test( .waitFor({ state: "visible", timeout: 5000 }); await page.getByTestId("more-options-button_folder-beta").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); }, @@ -224,7 +229,9 @@ test( // Navigate to templates await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 30000, @@ -242,13 +249,13 @@ test( await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -270,9 +277,9 @@ test( .waitFor({ state: "visible", timeout: 5000 }); await page.getByTestId("more-options-button_folder-one").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); @@ -286,13 +293,13 @@ test( await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -317,9 +324,9 @@ test( .waitFor({ state: "visible", timeout: 5000 }); await page.getByTestId("more-options-button_folder-two").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); }, @@ -378,10 +385,10 @@ test( } await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); // Wait for deletion to complete - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 5000, }); @@ -403,9 +410,32 @@ test( await page.getByTestId("new_project_btn_empty_page").click(); + // The empty-state CTA can either open the templates modal directly + // (EmptyPageCommunity) or surface the FlowBuilderWelcome overlay + // (EmptyFolder → useStartNewFlow). Race both and click "Browse more" + // when the overlay shows up so we always end up on the templates modal. + await Promise.race([ + page.waitForSelector('[data-testid="modal-title"]', { timeout: 30000 }), + page.waitForSelector('[data-testid="flow-builder-welcome-panel"]', { + timeout: 30000, + }), + ]); + if ( + (await page + .locator('[data-testid="flow-builder-welcome-panel"]') + .count()) > 0 + ) { + await page.getByTestId("flow-builder-welcome-browse-more").click(); + await page.waitForSelector('[data-testid="modal-title"]', { + timeout: 30000, + }); + } + // Navigate to templates await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 30000, @@ -422,8 +452,10 @@ test( // Verify we can click on the folder and see the flow await page.getByTestId("sidebar-nav-Starter Project").click(); - // The folder should contain our newly created flow - await expect(page.getByTestId("list-card")).toBeVisible({ + // The folder should contain our newly created flow. Templates render an + // extra example list-card alongside the user's flow when the folder is + // freshly created, so scope to the first match to satisfy strict mode. + await expect(page.getByTestId("list-card").first()).toBeVisible({ timeout: 5000, }); }, diff --git a/src/frontend/tests/core/features/folders.spec.ts b/src/frontend/tests/core/features/folders.spec.ts index ef4d1d173e..936f262309 100644 --- a/src/frontend/tests/core/features/folders.spec.ts +++ b/src/frontend/tests/core/features/folders.spec.ts @@ -3,6 +3,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "CRUD folders", { tag: ["@release", "@api"] }, @@ -11,33 +12,32 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, }); await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByPlaceholder("Search flows").first().isVisible(); - await page.getByText("Flows").first().isVisible(); - if (await page.getByText("Components").first().isVisible()) { - await page.getByText("Components").first().isVisible(); + await expect(page.getByPlaceholder("Search flows").first()).toBeVisible(); + await expect(page.getByText("Flows").first()).toBeVisible(); + if (await page.getByText(TEXTS.labelComponents).first().isVisible()) { + await expect(page.getByText(TEXTS.labelComponents).first()).toBeVisible(); } else { - await page.getByText("MCP Server").first().isVisible(); + await expect(page.getByText("MCP Server").first()).toBeVisible(); } - await page.getByText("All").first().isVisible(); - await page.getByText("Select All").first().isVisible(); - await page.getByTestId("add-project-button").click(); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .isVisible(); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); @@ -62,8 +62,8 @@ test( await page.getByTestId("more-options-button_new-project-test-name").click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); - await expect(page.getByText("Project deleted successfully")).toBeVisible({ + await page.getByText(TEXTS.delete).last().click(); + await expect(page.getByText(TEXTS.toastProjectDeleted)).toBeVisible({ timeout: 3000, }); }, @@ -138,7 +138,9 @@ test("change flow folder", async ({ page }) => { // unique so our assertions can't collide with any template that // Starter ships with by default. await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, @@ -155,12 +157,12 @@ test("change flow folder", async ({ page }) => { await page.getByTestId("add-project-button").click(); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .waitFor({ state: "visible", timeout: 10000 }); await page .locator("[data-testid='project-sidebar']") - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .last() .dblclick(); await page.getByTestId("input-project").fill(destinationProjectName); diff --git a/src/frontend/tests/core/features/freeze-path.spec.ts b/src/frontend/tests/core/features/freeze-path.spec.ts index 699fc73154..7b321a6f73 100644 --- a/src/frontend/tests/core/features/freeze-path.spec.ts +++ b/src/frontend/tests/core/features/freeze-path.spec.ts @@ -6,6 +6,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to freeze a path", { tag: ["@release", "@workspace", "@components"] }, @@ -31,7 +32,9 @@ test( } await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); @@ -40,7 +43,7 @@ test( const randomSeed1 = Math.random().toString(36).substring(2, 10); const randomSeed2 = Math.random().toString(36).substring(2, 10); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await page .getByTestId("textarea_str_input_value") @@ -57,7 +60,7 @@ test( await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 60000, }); @@ -67,13 +70,13 @@ test( .click(); const randomTextGeneratedByAI = await page - .getByPlaceholder("Empty") + .getByPlaceholder(TEXTS.placeholderEmpty) .first() .inputValue(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); // Use a completely different prompt to ensure different output await page @@ -88,7 +91,7 @@ test( }); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 60000, }); @@ -98,15 +101,15 @@ test( .click(); const secondRandomTextGeneratedByAI = await page - .getByPlaceholder("Empty") + .getByPlaceholder(TEXTS.placeholderEmpty) .first() .inputValue(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); const languageModelNode = page .locator(".react-flow__node", { - has: page.getByText("Language Model", { exact: true }), + has: page.getByText(TEXTS.componentLanguageModel, { exact: true }), }) .last(); @@ -133,7 +136,7 @@ test( await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 60000, }); @@ -143,11 +146,11 @@ test( .click(); const thirdRandomTextGeneratedByAI = await page - .getByPlaceholder("Empty") + .getByPlaceholder(TEXTS.placeholderEmpty) .first() .inputValue(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); // The frozen path should return the cached (second) result, not generate new output expect(secondRandomTextGeneratedByAI).toEqual(thirdRandomTextGeneratedByAI); diff --git a/src/frontend/tests/core/features/freeze.spec.ts b/src/frontend/tests/core/features/freeze.spec.ts index e54ae6b573..a9ae494cf8 100644 --- a/src/frontend/tests/core/features/freeze.spec.ts +++ b/src/frontend/tests/core/features/freeze.spec.ts @@ -4,6 +4,7 @@ import { addLegacyComponents } from "../../utils/add-legacy-components"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to freeze a component", { tag: ["@release", "@workspace", "@components"] }, @@ -24,7 +25,7 @@ test( await addLegacyComponents(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); await page.waitForSelector('[data-testid="input_outputText Input"]', { timeout: 1000, }); @@ -45,11 +46,13 @@ test( await page.getByTestId("output-inspection-output text-textinput").click(); - const firstOutputText = await page.getByPlaceholder("Empty").textContent(); + const firstOutputText = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .textContent(); expect(firstOutputText).toBe("hello world"); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.getByTestId("textarea_str_input_value").fill("goodbye world"); @@ -76,7 +79,9 @@ test( await page.getByTestId("output-inspection-output text-textinput").click(); - const secondOutputText = await page.getByPlaceholder("Empty").textContent(); + const secondOutputText = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .textContent(); expect(secondOutputText).toBe("goodbye world"); }, diff --git a/src/frontend/tests/core/features/globalVariables.spec.ts b/src/frontend/tests/core/features/globalVariables.spec.ts index 2efea5db08..d5197ced63 100644 --- a/src/frontend/tests/core/features/globalVariables.spec.ts +++ b/src/frontend/tests/core/features/globalVariables.spec.ts @@ -5,6 +5,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to save or delete a global variable", { tag: ["@release", "@workspace", "@api"] }, @@ -17,7 +18,9 @@ test( }); await page.getByTestId("blank-flow").click(); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.providerOpenAiSearch); await page.waitForSelector('[data-testid="openaiOpenAI"]', { timeout: 1000, @@ -46,7 +49,7 @@ test( await page.getByTestId("icon-Globe").nth(0).click(); await page.getByText("Add New Variable", { exact: true }).click(); await page - .getByPlaceholder("Enter a name for the variable...") + .getByPlaceholder(TEXTS.placeholderVariableName) .fill(genericName); await page.getByText("Generic", { exact: true }).first().isVisible(); await page @@ -58,7 +61,7 @@ test( await page.getByText("Add New Variable", { exact: true }).click(); await page - .getByPlaceholder("Enter a name for the variable...") + .getByPlaceholder(TEXTS.placeholderVariableName) .fill(credentialName); await page.getByTestId("credential-tab").click(); await page @@ -73,7 +76,7 @@ test( .hover() .then(async () => { await page.getByTestId("icon-Trash2").last().click(); - await page.getByText("Delete", { exact: true }).nth(1).click(); + await page.getByText(TEXTS.delete, { exact: true }).nth(1).click(); }); }, ); diff --git a/src/frontend/tests/core/features/group.spec.ts b/src/frontend/tests/core/features/group.spec.ts deleted file mode 100644 index 98715c99d2..0000000000 --- a/src/frontend/tests/core/features/group.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test.describe("group node test", () => { - /// - // TODO: fix this test - test.skip( - "group and ungroup updating values", - { tag: ["@release", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Basic Prompting" }) - .first() - .click(); - await page.getByTestId("fit_view").first().click(); - - await page.getByTestId("title-OpenAI").click(); - await page - .getByTestId("title-OpenAI") - .click({ modifiers: ["ControlOrMeta"] }); - await page - .getByTestId("title-Prompt Template") - .click({ modifiers: ["ControlOrMeta"] }); - await page - .getByTestId("title-OpenAI") - .click({ modifiers: ["ControlOrMeta"] }); - - await page.getByRole("button", { name: "Group" }).click(); - await page.getByTestId("title-Group").click(); - await page.getByTestId("edit-name-description-button").click(); - await page.getByTestId("input-title-Group").first().fill("test"); - await page.getByTestId("save-name-description-button").first().click(); - await page.keyboard.press("ControlOrMeta+g"); - await page.getByTestId("title-OpenAI").isVisible(); - await page.getByTestId("title-Prompt Template").isVisible(); - }, - ); -}); diff --git a/src/frontend/tests/core/features/modelInputComponent.spec.ts b/src/frontend/tests/core/features/modelInputComponent.spec.ts index 0e56a8c447..2d2f40dc6d 100644 --- a/src/frontend/tests/core/features/modelInputComponent.spec.ts +++ b/src/frontend/tests/core/features/modelInputComponent.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("ModelInputComponent", () => { test( "should display model selector in a node with model input", @@ -16,7 +17,7 @@ test.describe("ModelInputComponent", () => { // Search for OpenAI component which has model input await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); // Wait for search results await page.waitForTimeout(500); @@ -54,7 +55,7 @@ test.describe("ModelInputComponent", () => { }); // Add an OpenAI model node - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); await page.waitForTimeout(500); const openaiComponent = page.getByTestId("modelsOpenAI").first(); @@ -93,7 +94,7 @@ test.describe("ModelInputComponent", () => { }); // Add a model component - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); await page.waitForTimeout(500); const openaiComponent = page.getByTestId("modelsOpenAI").first(); @@ -133,7 +134,7 @@ test.describe("ModelInputComponent", () => { }); // Add a model component - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); await page.waitForTimeout(500); const openaiComponent = page.getByTestId("modelsOpenAI").first(); @@ -175,7 +176,7 @@ test.describe("ModelInputComponent", () => { }); // Add a model component - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); await page.waitForTimeout(500); const openaiComponent = page.getByTestId("modelsOpenAI").first(); @@ -212,7 +213,7 @@ test.describe("ModelInputComponent", () => { }); // Add a model component - await page.getByTestId("sidebar-search-input").fill("OpenAI"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.providerOpenAi); await page.waitForTimeout(500); const openaiComponent = page.getByTestId("modelsOpenAI").first(); diff --git a/src/frontend/tests/core/features/modelProviderCount.spec.ts b/src/frontend/tests/core/features/modelProviderCount.spec.ts deleted file mode 100644 index 921f34e96d..0000000000 --- a/src/frontend/tests/core/features/modelProviderCount.spec.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test.describe("ModelProviderCount Component", () => { - test( - "should open model provider page when navigating via settings", - { tag: ["@release", "@components", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - // Close the new project modal - const closeButton = page - .locator("button") - .filter({ hasText: "Close" }) - .or(page.locator('button[aria-label="Close"]')) - .or(page.locator('button[data-testid="close-button"]')) - .first(); - await closeButton.click(); - - // Navigate to settings > Model Providers - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - - // Wait for the settings page header - await page.waitForSelector('[data-testid="settings_menu_header"]', { - timeout: 5000, - }); - - // Page should appear with "Model Providers" header - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - - // Page should contain provider configuration content - await expect( - page.getByText( - "Configure AI model providers and manage their API keys.", - ), - ).toBeVisible(); - }, - ); - - test( - "should navigate back from model provider page", - { tag: ["@release", "@components", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - const closeButton = page - .locator("button") - .filter({ hasText: "Close" }) - .or(page.locator('button[aria-label="Close"]')) - .or(page.locator('button[data-testid="close-button"]')) - .first(); - await closeButton.click(); - - // Navigate to settings > Model Providers - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - - // Verify page is open - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - - // Navigate back by clicking the back button - await page.getByTestId("icon-ChevronLeft").first().click(); - - // Should be back at main view - the settings page header should change - await expect( - page.getByText( - "Configure AI model providers and manage their API keys.", - ), - ).not.toBeVisible({ timeout: 3000 }); - }, - ); - - test( - "should navigate to model provider page multiple times", - { tag: ["@release", "@components", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - const closeButton = page - .locator("button") - .filter({ hasText: "Close" }) - .or(page.locator('button[aria-label="Close"]')) - .or(page.locator('button[data-testid="close-button"]')) - .first(); - await closeButton.click(); - - // First navigation - open page - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - - // Navigate back - await page.getByTestId("icon-ChevronLeft").first().click(); - await expect( - page.getByText( - "Configure AI model providers and manage their API keys.", - ), - ).not.toBeVisible({ timeout: 3000 }); - - // Second navigation - open page again - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - }, - ); - - test( - "should display provider list in the page", - { tag: ["@release", "@components", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - const closeButton = page - .locator("button") - .filter({ hasText: "Close" }) - .or(page.locator('button[aria-label="Close"]')) - .or(page.locator('button[data-testid="close-button"]')) - .first(); - await closeButton.click(); - - // Navigate to settings > Model Providers - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - - // Wait for page to be fully visible - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - - // The page should contain provider selection area with content - await page.waitForTimeout(500); - - // Page should be properly structured with content - await expect(page.locator("div").first()).toBeVisible(); - }, - ); - - test( - "model provider page should display provider count information", - { tag: ["@release", "@components", "@workspace"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - const closeButton = page - .locator("button") - .filter({ hasText: "Close" }) - .or(page.locator('button[aria-label="Close"]')) - .or(page.locator('button[data-testid="close-button"]')) - .first(); - await closeButton.click(); - - // Navigate to settings > Model Providers - await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").first().click(); - await page.getByText("Model Providers").first().click(); - - // Page should display the header - await expect( - page.getByTestId("settings_menu_header").last(), - ).toContainText("Model Providers", { timeout: 5000 }); - - // Provider list content should be visible - const providerList = page.getByTestId("provider-list"); - if (await providerList.isVisible({ timeout: 3000 })) { - await expect(providerList).toBeVisible(); - } - }, - ); -}); diff --git a/src/frontend/tests/core/features/modelProviderModal.spec.ts b/src/frontend/tests/core/features/modelProviderModal.spec.ts index eeebcbd8b4..4afdc5d6d6 100644 --- a/src/frontend/tests/core/features/modelProviderModal.spec.ts +++ b/src/frontend/tests/core/features/modelProviderModal.spec.ts @@ -178,4 +178,35 @@ test.describe("ModelProviderModal", () => { ).toBeVisible(); }, ); + + test( + "should navigate to model provider page multiple times", + { tag: ["@release", "@components", "@workspace"] }, + async ({ page }) => { + await awaitBootstrapTest(page, { skipModal: true }); + + // Wait for page to be ready + await page.waitForTimeout(1000); + + // First navigation - open page + await navigateSettingsPages(page, "Settings", "Model Providers"); + await expect( + page.getByTestId("settings_menu_header").last(), + ).toContainText("Model Providers", { timeout: 5000 }); + + // Navigate back + await page.getByTestId("icon-ChevronLeft").first().click(); + await expect( + page.getByText( + "Configure AI model providers and manage their API keys.", + ), + ).not.toBeVisible({ timeout: 3000 }); + + // Second navigation - open page again + await navigateSettingsPages(page, "Settings", "Model Providers"); + await expect( + page.getByTestId("settings_menu_header").last(), + ).toContainText("Model Providers", { timeout: 5000 }); + }, + ); }); diff --git a/src/frontend/tests/core/features/playground.spec.ts b/src/frontend/tests/core/features/playground.spec.ts index ef7a225e1c..6f7e796deb 100644 --- a/src/frontend/tests/core/features/playground.spec.ts +++ b/src/frontend/tests/core/features/playground.spec.ts @@ -1,68 +1,45 @@ -import * as dotenv from "dotenv"; -import path from "path"; 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 { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { addComponentFromSidebar } from "../../utils/flow/add-component-from-sidebar"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; import { disableInspectPanel } from "../../utils/open-advanced-options"; +import { sessionMoreMenu } from "../../utils/playground/sessions"; +import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "fresh start playground", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + loadDotenvIfLocal(__dirname); - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await disableInspectPanel(page); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); - await page.waitForSelector('[data-testid="input_outputChat Output"]', { - timeout: 100000, + await addComponentFromSidebar(page, { + search: "chat output", + testId: "input_outputChat Output", + hoverAdd: true, }); - await page - .getByTestId("input_outputChat Output") - .hover() - .then(async () => { - await page.getByTestId("add-component-button-chat-output").click(); - }); - await zoomOut(page, 2); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); - await page.waitForSelector('[data-testid="input_outputChat Input"]', { - timeout: 100000, + await addComponentFromSidebar(page, { + search: "chat input", + testId: "input_outputChat Input", + position: { x: 100, y: 100 }, }); - await page - .getByTestId("input_outputChat Input") - .dragTo(page.locator('//*[@id="react-flow-id"]'), { - targetPosition: { x: 100, y: 100 }, - }); - - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); - await page.waitForSelector('[data-testid="input_outputText Output"]', { - timeout: 100000, + await addComponentFromSidebar(page, { + search: "text output", + testId: "input_outputText Output", + position: { x: 300, y: 300 }, }); - await page - .getByTestId("input_outputText Output") - .dragTo(page.locator('//*[@id="react-flow-id"]'), { - targetPosition: { x: 300, y: 300 }, - }); - await adjustScreenView(page); await page @@ -78,14 +55,16 @@ test( .getByTestId("handle-chatoutput-noshownode-inputs-target") .click(); - await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.waitForSelector('[data-testid="input-chat-playground"]', { - timeout: 100000, + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page.waitForSelector(`[data-testid="${TID.inputChatPlayground}"]`, { + timeout: TIMEOUTS.componentMount, }); //send message - await page.getByTestId("input-chat-playground").click(); - await page.getByTestId("input-chat-playground").fill("message 1"); + await page.getByTestId(TID.inputChatPlayground).click(); + await page.getByTestId(TID.inputChatPlayground).fill("message 1"); await page.keyboard.press("Enter"); await expect(page.getByTestId("chat-message-User-message 1")).toBeVisible(); await expect(page.getByTestId("chat-message-AI-message 1")).toBeVisible(); @@ -119,63 +98,57 @@ test( await expect(page.getByTestId("chat-message-AI-edit_bot_1")).toBeVisible(); // check table messages view (use sidebar session more menu — header menu hidden in fullscreen) - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .first() - .click(); + await sessionMoreMenu(page, "first").click(); await page.getByTestId("message-logs-option").click(); await expect(page.getByText("Page 1 of 1", { exact: true })).toBeVisible(); - await page.getByRole("button", { name: "Close" }).click(); + await page.getByRole("button", { name: TEXTS.close }).click(); // create new session (use sidebar new-chat button) - await page.getByTestId("new-chat").click(); + await page.getByTestId(TID.newChat).click(); await expect(page.getByTitle("New Session 0")).toBeVisible(); // check rename session await page - .getByTestId("input-chat-playground") + .getByTestId(TID.inputChatPlayground) .fill("session_after_delete"); await page.keyboard.press("Enter"); await page .getByTestId("chat-message-User-session_after_delete") .isVisible(); // Use sidebar session more menu for rename - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .last() - .click(); + await sessionMoreMenu(page, "last").click(); await page.getByTestId("rename-session-option").click(); await page.getByTestId("session-rename-input").fill("my first session"); await page.keyboard.press("Enter"); await expect( - page.getByTestId("session-selector").getByText("my first session"), - ).toBeVisible(); + page + .getByTestId(TID.sessionSelector) + .filter({ hasText: "my first session" }) + .first(), + ).toBeVisible({ timeout: 10000 }); // check cancel rename (using Escape key) - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .last() - .click(); + await sessionMoreMenu(page, "last").click(); await page.getByTestId("rename-session-option").click(); await page.getByTestId("session-rename-input").fill("cancel name"); await page.keyboard.press("Escape"); await expect( - page.getByTestId("session-selector").getByText("my first session"), - ).toBeVisible(); + page + .getByTestId(TID.sessionSelector) + .filter({ hasText: "my first session" }) + .first(), + ).toBeVisible({ timeout: 10000 }); // check delete session - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .last() - .click(); + await sessionMoreMenu(page, "last").click(); await page.getByTestId("delete-session-option").click(); await expect(page.getByTitle("Default Session")).toBeVisible(); //create new session - await page.getByTestId("new-chat").click(); - await page.getByTestId("input-chat-playground").click(); + await page.getByTestId(TID.newChat).click(); + await page.getByTestId(TID.inputChatPlayground).click(); await page - .getByTestId("input-chat-playground") + .getByTestId(TID.inputChatPlayground) .fill("session_after_delete"); await page.keyboard.press("Enter"); await expect( diff --git a/src/frontend/tests/core/features/publish-flow.spec.ts b/src/frontend/tests/core/features/publish-flow.spec.ts index c20c794419..c0d9723a52 100644 --- a/src/frontend/tests/core/features/publish-flow.spec.ts +++ b/src/frontend/tests/core/features/publish-flow.spec.ts @@ -1,107 +1,85 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { ANIMATIONS, TIMEOUTS } from "../../utils/constants/timeouts"; +import { TID } from "../../utils/constants/testIds"; +import { addComponentFromSidebar } from "../../utils/flow/add-component-from-sidebar"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to publish a flow", { tag: ["@release", "@workspace", "@api"] }, async ({ page, context }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 5000, + await openBlankFlow(page); + await page.waitForSelector(`[data-testid="${TID.sidebarSearchInput}"]`, { + timeout: TIMEOUTS.short, }); - await page.getByTestId("blank-flow").click(); - await page.waitForSelector('[data-testid="sidebar-search-input"]', { - timeout: 5000, + await addComponentFromSidebar(page, { + search: "chat input", + testId: "input_outputChat Input", + hoverAdd: true, }); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); - - await page.waitForSelector('[data-testid="input_outputChat Input"]', { - timeout: 3000, - }); - - await page - .getByTestId("input_outputChat Input") - .hover({ timeout: 3000 }) - .then(async () => { - await page - .getByTestId("add-component-button-chat-input") - .last() - .click(); - }); - - await page.waitForTimeout(2000); - await adjustScreenView(page, { numberOfZoomOut: 3 }); - await page.getByTestId("publish-button").click(); + await page.getByTestId(TID.publishButton).click(); - await page.waitForTimeout(3000); - - await page.waitForSelector('[data-testid="shareable-playground"]', { - timeout: 10000, + await page.waitForSelector(`[data-testid="${TID.shareablePlayground}"]`, { + timeout: TIMEOUTS.medium, }); - try { - await page.waitForTimeout(2000); + await expect(page.getByTestId(TID.publishSwitch)).toBeVisible({ + timeout: TIMEOUTS.medium, + }); - await expect(page.getByTestId("publish-switch")).toBeVisible({ - timeout: 10000, - }); - } catch (error) { - console.error("Error waiting for publish operation:", error); - throw error; - } - - await page.waitForTimeout(2000); - - await page.getByTestId("publish-switch").click(); + await page.getByTestId(TID.publishSwitch).click(); const pagePromise = context.waitForEvent("page"); + await page.waitForTimeout(ANIMATIONS.publishTogglePropagation); - await page.waitForTimeout(2000); - - await page.getByTestId("shareable-playground").click(); + await page.getByTestId(TID.shareablePlayground).click(); const newPage = await pagePromise; await newPage.waitForLoadState("domcontentloaded"); + // Wait for the chat input to actually be present before filling. The // default actionTimeout (20s) was not enough on Windows CI for the // shareable-playground page to mount the message input. await newPage - .getByPlaceholder("Send a message...") - .waitFor({ state: "visible", timeout: 60000 }); + .getByPlaceholder(TEXTS.placeholderSendMessage) + .waitFor({ state: "visible", timeout: TIMEOUTS.long }); const newUrl = newPage.url(); - await newPage.getByPlaceholder("Send a message...").fill("Hello"); - await newPage.getByTestId("button-send").last().click(); + await newPage.getByPlaceholder(TEXTS.placeholderSendMessage).fill("Hello"); + await newPage.getByTestId(TID.buttonSend).last().click(); - const stopButton = newPage.getByRole("button", { name: "Stop" }); - await stopButton.waitFor({ state: "visible", timeout: 30000 }); + const stopButton = newPage.getByRole("button", { name: TEXTS.stop }); + await stopButton.waitFor({ state: "visible", timeout: TIMEOUTS.standard }); await newPage.close(); await page.bringToFront(); - await page.waitForTimeout(500); - await page.getByTestId("publish-button").click(); - await page.waitForTimeout(500); - await page.getByTestId("publish-switch").click(); - await page.waitForTimeout(500); + await page.getByTestId(TID.publishButton).click(); + await page.getByTestId(TID.publishSwitch).click(); await expect(page.getByTestId("rf__wrapper")).toBeVisible(); - await expect(page.getByTestId("publish-switch")).toBeChecked({ + await expect(page.getByTestId(TID.publishSwitch)).toBeChecked({ checked: false, }); await expect(page.getByTestId("rf__wrapper")).toBeVisible(); + + // The publish-switch toggle is confirmed in the UI above, but the + // un-publish has to reach the backend before the shareable URL stops + // resolving — give it time to propagate. + await page.waitForTimeout(ANIMATIONS.publishTogglePropagation); + await page.goto(newUrl); - await page.waitForTimeout(2000); - try { - await expect(page.getByTestId("mainpage_title")).toBeVisible({ - timeout: 10000, - }); - } catch (_error) { - await page.reload(); - await expect(page.getByTestId("mainpage_title")).toBeVisible({ - timeout: 10000, - }); - } + + // An un-published playground URL redirects to the projects page: + // PlaygroundPage detects access_type !== "PUBLIC" and navigates to + // "/". The redirect is client-side and the subsequent app bootstrap + // can be slow on CI, so wait for the URL to leave /playground/ + // before asserting the projects page rendered. + await page.waitForURL((url) => !url.pathname.startsWith("/playground/"), { + timeout: TIMEOUTS.long, + }); + await expect(page.getByTestId(TID.mainpageTitle)).toBeVisible({ + timeout: TIMEOUTS.long, + }); }, ); diff --git a/src/frontend/tests/core/features/right-click-dropdown.spec.ts b/src/frontend/tests/core/features/right-click-dropdown.spec.ts index 3a91525efd..68fe0bd264 100644 --- a/src/frontend/tests/core/features/right-click-dropdown.spec.ts +++ b/src/frontend/tests/core/features/right-click-dropdown.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user can open component dropdown menu by right-clicking on nodes", { tag: ["@release", "@components", "@dropdown", "@right-click"] }, @@ -10,7 +11,9 @@ test( // Start with a basic template that has multiple components if (await page.getByTestId("components-btn").isVisible()) { await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); } await page.getByTestId("template-get-started-card-basic-prompting").click(); @@ -29,7 +32,7 @@ test( }); // Test 1: Right-click on Chat Input component should open dropdown immediately (single click) - const chatInputComponent = page.getByText("Chat Input").first(); + const chatInputComponent = page.getByText(TEXTS.componentChatInput).first(); // First, click somewhere else to ensure no component is selected await page.click("body", { position: { x: 100, y: 100 } }); diff --git a/src/frontend/tests/core/features/run-flow.spec.ts b/src/frontend/tests/core/features/run-flow.spec.ts index be079aa32e..716db02ca7 100644 --- a/src/frontend/tests/core/features/run-flow.spec.ts +++ b/src/frontend/tests/core/features/run-flow.spec.ts @@ -5,6 +5,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to use Run Flow without any issues", { tag: ["@release", "@workspace", "@api"] }, @@ -22,7 +23,7 @@ test( await page.getByTestId("blank-flow").click(); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 100000, }); @@ -37,7 +38,7 @@ test( await zoomOut(page, 2); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatInput); await page.waitForSelector('[data-testid="input_outputChat Input"]', { timeout: 100000, }); @@ -49,7 +50,7 @@ test( }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page.waitForSelector('[data-testid="input_outputText Output"]', { timeout: 100000, }); @@ -77,7 +78,7 @@ test( await page.getByTestId("icon-ChevronLeft").click(); - await page.getByText("New Flow").isVisible(); + await expect(page.getByText("New Flow")).toBeVisible(); await page.getByTestId("new-project-btn").click(); await page.getByTestId("blank-flow").click(); @@ -116,7 +117,7 @@ test( .fill("THIS IS A TEST FOR RUN FLOW COMPONENT"); await page.getByTestId("button_run_run flow").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000, }); @@ -127,7 +128,9 @@ test( await page.locator('[data-testid^="output-inspection-"]').first().click(); - const value = await page.getByPlaceholder("Empty").inputValue(); + const value = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .inputValue(); expect(value).toBe("THIS IS A TEST FOR RUN FLOW COMPONENT"); }, diff --git a/src/frontend/tests/core/features/saveComponents.spec.ts b/src/frontend/tests/core/features/saveComponents.spec.ts deleted file mode 100644 index 7d64ad646a..0000000000 --- a/src/frontend/tests/core/features/saveComponents.spec.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { readFileSync } from "fs"; -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"; - -test.describe("save component tests", () => { - /// - test.skip( - "save group component tests", - { tag: ["@release", "@workspace", "@api"] }, - - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); - - // Read your file into a buffer. - const jsonContent = readFileSync( - "tests/assets/flow_group_test.json", - "utf-8", - ); - - // Create the DataTransfer and File - const dataTransfer = await page.evaluateHandle((data) => { - const dt = new DataTransfer(); - // Convert the buffer to a hex array - const file = new File([data], "flow_group_test.json", { - type: "application/json", - }); - dt.items.add(file); - return dt; - }, jsonContent); - - // Now dispatch - await page.dispatchEvent( - "//*[@data-testid='rf__wrapper']/div[1]/div", - "drop", - { - dataTransfer, - }, - ); - - const genericNoda = page.getByTestId("div-generic-node"); - const elementCount = await genericNoda?.count(); - if (elementCount > 0) { - expect(true).toBeTruthy(); - } - - await adjustScreenView(page, { numberOfZoomOut: 2 }); - - await page.getByTestId("title-Agent Initializer").click({ - modifiers: ["Control"], - }); - - await page.getByRole("button", { name: "Group" }).click(); - - await page - .locator('//*[@id="react-flow-id"]') - .first() - .click({ button: "left" }); - - let textArea = page.getByTestId("div-textarea-description"); - let elementCountText = await textArea?.count(); - if (elementCountText > 0) { - expect(true).toBeTruthy(); - } - - let groupNode = page.getByTestId("title-Group"); - let elementGroup = await groupNode?.count(); - if (elementGroup > 0) { - expect(true).toBeTruthy(); - } - - await page.getByTestId("title-Group").click(); - await page.getByTestId("more-options-modal").click(); - - await page.getByTestId("icon-SaveAll").click(); - // timeout to handle case where there is already a saved component with the same name - await page.waitForTimeout(1000); - - const replaceButton = await page - .getByTestId("replace-button") - .isVisible(); - - if (replaceButton) { - await page.getByTestId("replace-button").click(); - } - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("group"); - - await page - .getByText("Group") - .first() - .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); - await page.getByTestId("fit_view").click(); - textArea = page.getByTestId("div-textarea-description"); - elementCountText = await textArea?.count(); - if (elementCountText > 0) { - expect(true).toBeTruthy(); - } - - groupNode = page.getByTestId("title-Group"); - elementGroup = await groupNode?.count(); - if (elementGroup > 0) { - expect(true).toBeTruthy(); - } - }, - ); -}); diff --git a/src/frontend/tests/core/features/shareable-playground-auth.spec.ts b/src/frontend/tests/core/features/shareable-playground-auth.spec.ts index 019344e50c..e6b23564a7 100644 --- a/src/frontend/tests/core/features/shareable-playground-auth.spec.ts +++ b/src/frontend/tests/core/features/shareable-playground-auth.spec.ts @@ -1,84 +1,29 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; - -/** - * Helper: create Basic Prompting flow, configure GPT, publish, - * and open the shareable playground in a new tab. - */ -async function setupShareablePlayground(page: any, context: any): Promise { - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); - - await initialGPTsetup(page); - - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByTestId("publish-button").click(); - await page.waitForSelector('[data-testid="shareable-playground"]', { - timeout: 10000, - }); - await page.waitForTimeout(1000); - await page.getByTestId("publish-switch").click(); - await page.waitForTimeout(2000); - - const pagePromise = context.waitForEvent("page"); - await page.getByTestId("shareable-playground").click(); - const newPage = await pagePromise; - await newPage.waitForTimeout(3000); - - return newPage; -} - -/** - * Helper: send message and wait for build to complete. - * Uses the same pattern as publish-flow.spec.ts (Stop button lifecycle). - */ -async function sendAndWaitForResponse(playgroundPage: any, message: string) { - // Wait for the chat input to be present. The default actionTimeout (20s) - // was not enough on Windows CI for the shareable-playground page to mount - // the message input. - await playgroundPage - .getByPlaceholder("Send a message...") - .waitFor({ state: "visible", timeout: 60000 }); - await playgroundPage.getByPlaceholder("Send a message...").fill(message); - await playgroundPage.getByTestId("button-send").last().click(); - - // Wait for Stop button to appear (build started) - const stopButton = playgroundPage.getByRole("button", { name: "Stop" }); - await stopButton.waitFor({ state: "visible", timeout: 30000 }); - - // Wait for Stop button to disappear (build complete) - await stopButton.waitFor({ state: "hidden", timeout: 120000 }); -} +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { publishBasicPromptingAndOpenShareablePlayground } from "../../utils/playground/publish-and-open-shareable"; +import { sendPlaygroundMessage } from "../../utils/playground/send-playground-message"; test( "shareable playground: auto-login user can send message and get response", { tag: ["@release", "@workspace", "@api"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); - const playgroundPage = await setupShareablePlayground(page, context); - - await sendAndWaitForResponse(playgroundPage, "Say hello"); + await sendPlaygroundMessage(playgroundPage, "Say hello", { + surface: "shareable", + }); // After build complete, at least one chat message should be visible await expect( - playgroundPage.locator('[data-testid="div-chat-message"]').first(), - ).toBeVisible({ timeout: 10000 }); + playgroundPage.locator(`[data-testid="${TID.chatMessage}"]`).first(), + ).toBeVisible({ timeout: TIMEOUTS.medium }); await playgroundPage.close(); }, @@ -88,40 +33,19 @@ test( "shareable playground: streaming works", { tag: ["@release", "@workspace", "@api"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); - const playgroundPage = await setupShareablePlayground(page, context); - - // Wait for the chat input to be present (Windows CI is slow to mount it). - await playgroundPage - .getByPlaceholder("Send a message...") - .waitFor({ state: "visible", timeout: 60000 }); - await playgroundPage - .getByPlaceholder("Send a message...") - .fill("Tell me a short joke"); - await playgroundPage.getByTestId("button-send").last().click(); - - // Stop button should appear during build (streaming active) - await playgroundPage.waitForSelector('[data-testid="button-stop"]', { - timeout: 30000, + await sendPlaygroundMessage(playgroundPage, "Tell me a short joke", { + surface: "shareable", }); - // Wait for stop button to disappear (streaming finished) - await playgroundPage.waitForSelector('[data-testid="button-stop"]', { - state: "detached", - timeout: 120000, - }); - - // Send button should be back + // After stop button disappears the send button should be back await expect( - playgroundPage.getByTestId("button-send").last(), + playgroundPage.getByTestId(TID.buttonSend).last(), ).toBeVisible(); await playgroundPage.close(); @@ -132,31 +56,26 @@ test( "shareable playground: session management works", { tag: ["@release", "@workspace", "@api"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); - const playgroundPage = await setupShareablePlayground(page, context); - - await sendAndWaitForResponse(playgroundPage, "Session test"); + await sendPlaygroundMessage(playgroundPage, "Session test", { + surface: "shareable", + }); // Create new session - await playgroundPage.getByTestId("new-chat").click(); - await playgroundPage.waitForTimeout(2000); + await playgroundPage.getByTestId(TID.newChat).click(); - // New session should be created — at least "Default Session" + new one - // Check that the new-chat button is still clickable (UI didn't crash) - await expect(playgroundPage.getByTestId("new-chat")).toBeVisible(); + // New session should be created and the UI should not crash + await expect(playgroundPage.getByTestId(TID.newChat)).toBeVisible(); // Verify at least one session-selector exists await expect( - playgroundPage.getByTestId("session-selector").first(), - ).toBeVisible({ timeout: 5000 }); + playgroundPage.getByTestId(TID.sessionSelector).first(), + ).toBeVisible({ timeout: TIMEOUTS.medium }); await playgroundPage.close(); }, diff --git a/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts b/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts index c07da4a3d6..d59594a5ae 100644 --- a/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts +++ b/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts @@ -1,161 +1,86 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; - // These tests require the server to run with AUTO_LOGIN=FALSE. // When the server runs with AUTO_LOGIN=TRUE (default for local dev), // the backend uses client_id instead of user_id for session isolation, // and persistence features are not active. // Set LANGFLOW_AUTO_LOGIN=false in your .env to run these tests. -/** - * Helper: mock auto-login as disabled and log in manually. - */ -async function setupAutoLoginOff(page: any) { - await page.route("**/api/v1/auto_login", (route: any) => { - route.fulfill({ - status: 500, - contentType: "application/json", - body: JSON.stringify({ detail: { auto_login: false } }), - }); - }); +import type { Page } from "@playwright/test"; +import { expect, test } from "../../fixtures"; +import { mockAutoLoginDisabled } from "../../utils/auth/mock-auto-login-disabled"; +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { publishBasicPromptingAndOpenShareablePlayground } from "../../utils/playground/publish-and-open-shareable"; +import { sendPlaygroundMessage } from "../../utils/playground/send-playground-message"; +import { sessionMoreMenu } from "../../utils/playground/sessions"; - await page.addInitScript(() => { - window.process = window.process || ({} as any); - const newEnv = { - ...(window.process as any).env, - LANGFLOW_AUTO_LOGIN: "false", - }; - Object.defineProperty(window.process, "env", { - value: newEnv, - writable: true, - configurable: true, - }); - sessionStorage.setItem("testMockAutoLogin", "true"); - }); +import { TEXTS } from "../../utils/constants/texts"; +/** + * Stub auto-login, navigate to the sign-in page, log in manually. + */ +async function setupAutoLoginOff(page: Page): Promise { + await mockAutoLoginDisabled(page); await page.goto("/"); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: TIMEOUTS.standard, + }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); - await page.waitForSelector('[data-testid="mainpage_title"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.mainpageTitle}"]`, { + timeout: TIMEOUTS.standard, }); } -/** - * Helper: create Basic Prompting flow, configure GPT, publish, - * and return the shareable playground URL. - */ -async function createPublishAndGetUrl( - page: any, - context: any, -): Promise { - await page.waitForSelector('[id="new-project-btn"]', { timeout: 30000 }); - - await awaitBootstrapTest(page, { skipGoto: true }); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); - - await initialGPTsetup(page); - - // Build first - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - // Publish - await page.getByTestId("publish-button").click(); - await page.waitForSelector('[data-testid="shareable-playground"]', { - timeout: 10000, - }); - await page.waitForTimeout(1000); - await page.getByTestId("publish-switch").click(); - await page.waitForTimeout(2000); - - // Get URL - const pagePromise = context.waitForEvent("page"); - await page.getByTestId("shareable-playground").click(); - const newPage = await pagePromise; - await newPage.waitForTimeout(2000); - const url = newPage.url(); - await newPage.close(); - - return url; -} - -/** - * Helper: send a message and wait for the build to complete. - */ -async function sendMessageAndWait(page: any, message: string) { - await page.getByPlaceholder("Send a message...").fill(message); - await page.getByTestId("button-send").last().click(); - - // Wait for Stop button lifecycle (build started → completed) - const stopButton = page.getByRole("button", { name: "Stop" }); - await stopButton.waitFor({ state: "visible", timeout: 30000 }); - await stopButton.waitFor({ state: "hidden", timeout: 120000 }); - - await page.waitForTimeout(2000); -} - test( "shareable playground: logged-in user messages persist after page refresh", { tag: ["@release", "@api", "@database"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - process?.env?.LANGFLOW_AUTO_LOGIN !== "false", - "Server must run with AUTO_LOGIN=FALSE for persistence tests", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + skipIfMissing.autoLoginDisabled(); + loadDotenvIfLocal(__dirname); await setupAutoLoginOff(page); - const playgroundUrl = await createPublishAndGetUrl(page, context); + const { url: playgroundUrl, playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context, { + skipBootstrap: true, + }); + await playgroundPage.close(); - // Navigate to shareable playground await page.goto(playgroundUrl); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - // Send message - await sendMessageAndWait(page, "persist test"); + await sendPlaygroundMessage(page, "persist test", { + surface: "shareable", + }); - // Should have messages const messagesBefore = await page - .locator('[data-testid="div-chat-message"]') + .locator(`[data-testid="${TID.chatMessage}"]`) .count(); expect(messagesBefore).toBeGreaterThanOrEqual(2); - // Refresh await page.reload(); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - await page.waitForTimeout(3000); - // Messages should still be visible const messagesAfter = await page - .locator('[data-testid="div-chat-message"]') + .locator(`[data-testid="${TID.chatMessage}"]`) .count(); expect(messagesAfter).toBeGreaterThanOrEqual(2); }, @@ -165,37 +90,31 @@ test( "shareable playground: default session appears first", { tag: ["@release", "@api", "@database"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - process?.env?.LANGFLOW_AUTO_LOGIN !== "false", - "Server must run with AUTO_LOGIN=FALSE for persistence tests", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + skipIfMissing.autoLoginDisabled(); + loadDotenvIfLocal(__dirname); await setupAutoLoginOff(page); - const playgroundUrl = await createPublishAndGetUrl(page, context); + const { url: playgroundUrl, playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context, { + skipBootstrap: true, + }); + await playgroundPage.close(); await page.goto(playgroundUrl); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - // Send message in default session - await sendMessageAndWait(page, "default session msg"); + await sendPlaygroundMessage(page, "default session msg", { + surface: "shareable", + }); // Create new session - await page.getByTestId("new-chat").click(); - await page.waitForTimeout(1000); + await page.getByTestId(TID.newChat).click(); // First session should be Default Session - const firstSession = page.getByTestId("session-selector").first(); + const firstSession = page.getByTestId(TID.sessionSelector).first(); await expect(firstSession).toContainText("Default Session"); }, ); @@ -204,60 +123,47 @@ test( "shareable playground: delete session persists after refresh", { tag: ["@release", "@api", "@database"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - process?.env?.LANGFLOW_AUTO_LOGIN !== "false", - "Server must run with AUTO_LOGIN=FALSE for persistence tests", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + skipIfMissing.autoLoginDisabled(); + loadDotenvIfLocal(__dirname); await setupAutoLoginOff(page); - const playgroundUrl = await createPublishAndGetUrl(page, context); + const { url: playgroundUrl, playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context, { + skipBootstrap: true, + }); + await playgroundPage.close(); await page.goto(playgroundUrl); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - // Send message - await sendMessageAndWait(page, "keep this"); + await sendPlaygroundMessage(page, "keep this", { surface: "shareable" }); - // Create new session and send message - await page.getByTestId("new-chat").click(); - await page.waitForTimeout(1000); - await sendMessageAndWait(page, "delete this"); + // Create new session and send a message in it + await page.getByTestId(TID.newChat).click(); + await sendPlaygroundMessage(page, "delete this", { surface: "shareable" }); - const sessionsBefore = await page.getByTestId("session-selector").count(); + const sessionsBefore = await page.getByTestId(TID.sessionSelector).count(); // Delete last session - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .last() - .click(); + await sessionMoreMenu(page, "last").click(); await page.getByTestId("delete-session-option").click(); - await page.waitForTimeout(2000); const sessionsAfterDelete = await page - .getByTestId("session-selector") + .getByTestId(TID.sessionSelector) .count(); expect(sessionsAfterDelete).toBeLessThan(sessionsBefore); // Refresh await page.reload(); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - await page.waitForTimeout(3000); const sessionsAfterRefresh = await page - .getByTestId("session-selector") + .getByTestId(TID.sessionSelector) .count(); expect(sessionsAfterRefresh).toBeLessThanOrEqual(sessionsAfterDelete); }, @@ -267,57 +173,45 @@ test( "shareable playground: rename session persists after refresh", { tag: ["@release", "@api", "@database"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - process?.env?.LANGFLOW_AUTO_LOGIN !== "false", - "Server must run with AUTO_LOGIN=FALSE for persistence tests", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + skipIfMissing.autoLoginDisabled(); + loadDotenvIfLocal(__dirname); await setupAutoLoginOff(page); - const playgroundUrl = await createPublishAndGetUrl(page, context); + const { url: playgroundUrl, playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context, { + skipBootstrap: true, + }); + await playgroundPage.close(); await page.goto(playgroundUrl); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); // Create new session and send message - await page.getByTestId("new-chat").click(); - await page.waitForTimeout(1000); - await sendMessageAndWait(page, "rename test"); + await page.getByTestId(TID.newChat).click(); + await sendPlaygroundMessage(page, "rename test", { surface: "shareable" }); // Rename session - await page - .locator('[data-testid^="session-"][data-testid$="-more-menu"]') - .last() - .click(); + await sessionMoreMenu(page, "last").click(); await page.getByTestId("rename-session-option").click(); await page.getByTestId("session-rename-input").fill("Custom Name"); await page.keyboard.press("Enter"); - await page.waitForTimeout(1000); await expect( - page.getByTestId("session-selector").getByText("Custom Name"), - ).toBeVisible({ timeout: 5000 }); + page.getByTestId(TID.sessionSelector).getByText("Custom Name"), + ).toBeVisible({ timeout: TIMEOUTS.medium }); // Refresh await page.reload(); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.standard, }); - await page.waitForTimeout(3000); // Renamed session should persist await expect( - page.getByTestId("session-selector").getByText("Custom Name"), - ).toBeVisible({ timeout: 10000 }); + page.getByTestId(TID.sessionSelector).getByText("Custom Name"), + ).toBeVisible({ timeout: TIMEOUTS.medium }); }, ); diff --git a/src/frontend/tests/core/features/shareable-playground-token-display.spec.ts b/src/frontend/tests/core/features/shareable-playground-token-display.spec.ts index 10c9443b55..bf2ff1549d 100644 --- a/src/frontend/tests/core/features/shareable-playground-token-display.spec.ts +++ b/src/frontend/tests/core/features/shareable-playground-token-display.spec.ts @@ -1,70 +1,39 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { publishBasicPromptingAndOpenShareablePlayground } from "../../utils/playground/publish-and-open-shareable"; +import { sendPlaygroundMessage } from "../../utils/playground/send-playground-message"; +import { TEXTS } from "../../utils/constants/texts"; test( "shareable playground: bot messages display token usage", { tag: ["@release", "@workspace", "@api"] }, async ({ page, context }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); - - await initialGPTsetup(page); - - // Build first - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - // Publish - await page.getByTestId("publish-button").click(); - await page.waitForSelector('[data-testid="shareable-playground"]', { - timeout: 10000, + await sendPlaygroundMessage(playgroundPage, "Say hi", { + surface: "shareable", }); - await page.waitForTimeout(1000); - await page.getByTestId("publish-switch").click(); - await page.waitForTimeout(2000); - - const pagePromise = context.waitForEvent("page"); - await page.getByTestId("shareable-playground").click(); - const newPage = await pagePromise; - await newPage.waitForTimeout(3000); - - // Send message - await newPage.getByPlaceholder("Send a message...").fill("Say hi"); - await newPage.getByTestId("button-send").last().click(); - - // Wait for build to complete (Stop button lifecycle) - const stopButton = newPage.getByRole("button", { name: "Stop" }); - await stopButton.waitFor({ state: "visible", timeout: 30000 }); - await stopButton.waitFor({ state: "hidden", timeout: 120000 }); - - // Wait for UI to settle - await newPage.waitForTimeout(3000); // Token count should be visible (Coins icon indicates token display) - await newPage.waitForSelector('[data-testid="icon-Coins"]', { - timeout: 30000, + await playgroundPage.waitForSelector(`[data-testid="${TID.iconCoins}"]`, { + timeout: TIMEOUTS.standard, }); - const coinsIcons = await newPage - .locator('[data-testid="icon-Coins"]') + const coinsIcons = await playgroundPage + .locator(`[data-testid="${TID.iconCoins}"]`) .count(); expect(coinsIcons).toBeGreaterThan(0); - await newPage.close(); + await playgroundPage.close(); }, ); @@ -72,48 +41,34 @@ test( "regular playground: Finished In with token display still works (regression)", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Basic Prompting"); await initialGPTsetup(page); - // Build - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); - // Open regular playground - await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.waitForSelector('[data-testid="input-chat-playground"]', { - timeout: 10000, + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page.waitForSelector(`[data-testid="${TID.inputChatPlayground}"]`, { + timeout: TIMEOUTS.medium, }); - // Send message - await page.getByTestId("input-chat-playground").click(); - await page.getByTestId("input-chat-playground").fill("Say hello briefly"); + await page.getByTestId(TID.inputChatPlayground).click(); + await page.getByTestId(TID.inputChatPlayground).fill("Say hello briefly"); await page.keyboard.press("Enter"); - // Wait for AI response await page.waitForFunction( () => document.querySelectorAll('[data-testid="div-chat-message"]').length >= 2, - { timeout: 120000 }, + { timeout: TIMEOUTS.buildComplete }, ); - // "Finished in" should appear (regular playground specific) await expect(page.getByText("Finished in")).toBeVisible({ - timeout: 30000, + timeout: TIMEOUTS.standard, }); }, ); diff --git a/src/frontend/tests/core/features/stop-building.spec.ts b/src/frontend/tests/core/features/stop-building.spec.ts index b2d6a22deb..e40b600fc1 100644 --- a/src/frontend/tests/core/features/stop-building.spec.ts +++ b/src/frontend/tests/core/features/stop-building.spec.ts @@ -7,6 +7,7 @@ import { removeOldApiKeys } from "../../utils/remove-old-api-keys"; import { updateOldComponents } from "../../utils/update-old-components"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; // TODO: fix this test test( "user must be able to stop a building", @@ -20,7 +21,7 @@ test( //first component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); await page .getByTestId("input_outputText Input") @@ -32,7 +33,7 @@ test( //second component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchUrl); await page .getByTestId("data_sourceURL") @@ -65,7 +66,7 @@ test( //fifth component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page .getByTestId("input_outputChat Output") diff --git a/src/frontend/tests/core/features/store-shard-2.spec.ts b/src/frontend/tests/core/features/store-shard-2.spec.ts index 61512a2be2..463e10d292 100644 --- a/src/frontend/tests/core/features/store-shard-2.spec.ts +++ b/src/frontend/tests/core/features/store-shard-2.spec.ts @@ -1,9 +1,10 @@ import * as dotenv from "dotenv"; import path from "path"; -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "should filter by tag", { tag: ["@release", "@api"] }, @@ -28,14 +29,13 @@ test( }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); @@ -66,7 +66,7 @@ test( await safeClick("tag-selector-Vector Store"); await safeClick("tag-selector-Memory"); - await page.getByText("Basic RAG").isVisible(); + await expect(page.getByText(TEXTS.templateBasicRag)).toBeVisible(); }, ); @@ -91,14 +91,13 @@ test("should share component with share button", async ({ page }) => { }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, }); @@ -114,7 +113,9 @@ test("should share component with share button", async ({ page }) => { const randomName = Math.random().toString(36).substring(2); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await renameFlow(page, { flowName: randomName }); @@ -123,33 +124,32 @@ test("should share component with share button", async ({ page }) => { }); await page.getByTestId("shared-button-flow").first().click(); - await page.getByText("Name:").isVisible(); - await page.getByText("Description:").isVisible(); - await page.getByText("Set workflow status to public").isVisible(); + await expect(page.getByText("Name:")).toBeVisible(); + await expect(page.getByText("Description:")).toBeVisible(); + await expect(page.getByText("Set workflow status to public")).toBeVisible(); await page .getByText( "Attention: API keys in specified fields are automatically removed upon sharing.", ) .isVisible(); - await page.getByText("Export").first().isVisible(); - await page.getByText("Share Flow").first().isVisible(); - + await expect(page.getByText("Export").first()).toBeVisible(); + await expect(page.getByText("Share Flow").first()).toBeVisible(); await page.waitForTimeout(3000); - await page.getByText("Agent").first().isVisible(); - await page.getByText("Memory").first().isVisible(); - await page.getByText("Chain").first().isVisible(); - await page.getByText("Vector Store").first().isVisible(); - await page.getByText("Prompt").last().isVisible(); + await expect(page.getByText("Agent").first()).toBeVisible(); + await expect(page.getByText("Memory").first()).toBeVisible(); + await expect(page.getByText("Chain").first()).toBeVisible(); + await expect(page.getByText("Vector Store").first()).toBeVisible(); + await expect(page.getByText("Prompt").last()).toBeVisible(); await page.getByTestId("public-checkbox").isChecked(); const flowName = await page.getByTestId("input-flow-name").inputValue(); const flowDescription = await page .getByPlaceholder("Flow description") .inputValue(); - await page.getByText(flowName).last().isVisible(); - await page.getByText(flowDescription).last().isVisible(); + await expect(page.getByText(flowName).last()).toBeVisible(); + await expect(page.getByText(flowDescription).last()).toBeVisible(); await page.waitForTimeout(1000); - await page.getByText("Flow shared successfully").last().isVisible(); + await expect(page.getByText("Flow shared successfully").last()).toBeVisible(); }); diff --git a/src/frontend/tests/core/features/structured-response-agent.spec.ts b/src/frontend/tests/core/features/structured-response-agent.spec.ts index 9b037704d3..e2e1514fab 100644 --- a/src/frontend/tests/core/features/structured-response-agent.spec.ts +++ b/src/frontend/tests/core/features/structured-response-agent.spec.ts @@ -6,6 +6,7 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { openAdvancedOptions } from "../../utils/open-advanced-options"; +import { TEXTS } from "../../utils/constants/texts"; /** * E2E coverage for the Native Structured Output feature on the Agent * component (CZL/MANUAL_TEST_NATIVE_STRUCTURED_OUTPUT.md). The tests @@ -40,7 +41,10 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); @@ -84,7 +88,10 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); @@ -126,7 +133,10 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); diff --git a/src/frontend/tests/core/features/token-usage.spec.ts b/src/frontend/tests/core/features/token-usage.spec.ts index a2db326271..aeac4322df 100644 --- a/src/frontend/tests/core/features/token-usage.spec.ts +++ b/src/frontend/tests/core/features/token-usage.spec.ts @@ -4,6 +4,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("Token Usage Tracking", () => { test( "node badge should show token count after running an LLM flow", @@ -21,7 +22,9 @@ test.describe("Token Usage Tracking", () => { await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); @@ -29,7 +32,7 @@ test.describe("Token Usage Tracking", () => { await initialGPTsetup(page); await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { @@ -73,7 +76,9 @@ test.describe("Token Usage Tracking", () => { await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); @@ -81,7 +86,7 @@ test.describe("Token Usage Tracking", () => { await initialGPTsetup(page); await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { diff --git a/src/frontend/tests/core/features/toolModeGroup.spec.ts b/src/frontend/tests/core/features/toolModeGroup.spec.ts deleted file mode 100644 index 6962f41250..0000000000 --- a/src/frontend/tests/core/features/toolModeGroup.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test.describe("group node test", () => { - /// - // TODO: fix this test - test.skip( - "group and ungroup updating values", - { tag: ["@release", "@workspace", "@components"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Basic Prompting" }) - .first() - .click(); - await page.getByTestId("fit_view").first().click(); - - await page - .getByTestId("title-OpenAI") - .click({ modifiers: ["ControlOrMeta"] }); - await page - .getByTestId("title-Prompt Template") - .click({ modifiers: ["ControlOrMeta"] }); - - await page.getByRole("button", { name: "Group" }).click(); - await page.getByTestId("title-Group").click(); - await expect(page.getByTestId("tool-mode-button")).toBeHidden(); - }, - ); -}); diff --git a/src/frontend/tests/core/features/traces.spec.ts b/src/frontend/tests/core/features/traces.spec.ts index d8704df2df5..a0f2dc8e61 100644 --- a/src/frontend/tests/core/features/traces.spec.ts +++ b/src/frontend/tests/core/features/traces.spec.ts @@ -3,6 +3,7 @@ import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "should able to see and interact with Traces", { tag: ["@release", "@workspace", "@api"] }, @@ -31,7 +32,9 @@ test( response.status() === 201, { timeout: 120000 }, ); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await flowCreatePromise; await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -99,7 +102,9 @@ test.skip( response.status() === 201, { timeout: 120000 }, ); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await flowCreatePromise; await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, diff --git a/src/frontend/tests/core/features/tweaksTest.spec.ts b/src/frontend/tests/core/features/tweaksTest.spec.ts index b622de8bd7..daeadc16cd 100644 --- a/src/frontend/tests/core/features/tweaksTest.spec.ts +++ b/src/frontend/tests/core/features/tweaksTest.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "curl_api_generation", { tag: ["@release", "@workspace", "@api"] }, @@ -10,7 +11,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); // Wait for the new-flow Loading state to clear before checking the // publish button — the canvas mounts only after the flow finishes // loading, which can outlast a 20s action timeout on Windows CI. @@ -33,7 +36,7 @@ test( expect(clipboardContent.length).toBeGreaterThan(0); await page.getByTestId("tweaks-button").click(); await page - .getByRole("heading", { name: "Language Model" }) + .getByRole("heading", { name: TEXTS.componentLanguageModel }) .locator("div") .first() .click(); @@ -44,7 +47,7 @@ test( await page.getByTestId("showstream").first().click(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.getByTestId("api_tab_curl").click(); await page.getByTestId("icon-Copy").click(); @@ -56,7 +59,7 @@ test( expect(oldValue).not.toBe(newValue); expect(clipboardContent2.length).toBeGreaterThan(clipboardContent.length); await awaitBootstrapTest(page, { skipModal: true }); - await page.getByText("Basic Prompting").first().click(); + await page.getByText(TEXTS.templateBasicPrompting).first().click(); await page.getByTestId("publish-button").click(); await page.getByTestId("api-access-item").click(); expect( @@ -119,7 +122,7 @@ test("check if tweaks are updating when someothing on the flow changes", async ( await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible(); await page.getByText("persist_directory_123123123!@#$&*(&%$@").isVisible(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.getByText("Python", { exact: true }).click(); diff --git a/src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts b/src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts index e5b6d19e72..317ac3a96f 100644 --- a/src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts +++ b/src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "flow state should be properly cleaned up between user sessions", { tag: ["@release", "@api", "@database"] }, @@ -43,9 +44,15 @@ test( // Log in as admin and create test user await page.goto("/"); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); @@ -55,7 +62,7 @@ test( response.url().includes("/api/v1/login") && response.status() === 200, { timeout: 60000 }, ), - page.getByRole("button", { name: "Sign In" }).click(), + page.getByRole("button", { name: TEXTS.signIn }).click(), ]); // mainpage_title only renders after the homepage data finishes loading, @@ -66,13 +73,16 @@ test( await page.getByTestId("user-profile-settings").click(); await page.getByText("Admin Page", { exact: true }).click(); await page.getByText("New User", { exact: true }).click(); - await page.getByPlaceholder("Username").last().fill(userAName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(userAName); await page.locator('input[name="password"]').fill(userAPassword); await page.locator('input[name="confirmpassword"]').fill(userAPassword); await page.waitForSelector("#is_active", { timeout: 1500 }); await page.locator("#is_active").click(); await expect(page.locator("#is_active")).toBeChecked(); - await page.getByText("Save", { exact: true }).click(); + await page.getByText(TEXTS.save, { exact: true }).click(); await page.waitForSelector("text=new user added", { timeout: 30000 }); // Log out from admin @@ -84,14 +94,16 @@ test( await page.evaluate(() => { sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); // ---- USER A SESSION ---- // Log in as User A - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); - await page.getByPlaceholder("Username").fill(userAName); - await page.getByPlaceholder("Password").fill(userAPassword); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); + await page.getByPlaceholder(TEXTS.placeholderUsername).fill(userAName); + await page.getByPlaceholder(TEXTS.placeholderPassword).fill(userAPassword); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); @@ -101,7 +113,7 @@ test( response.url().includes("/api/v1/login") && response.status() === 200, { timeout: 60000 }, ), - page.getByRole("button", { name: "Sign In" }).click(), + page.getByRole("button", { name: TEXTS.signIn }).click(), ]); // Create a flow for User A @@ -159,14 +171,20 @@ test( await page.evaluate(() => { sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); // ---- ADMIN SESSION AGAIN ---- // Log in as admin again - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); @@ -176,7 +194,7 @@ test( response.url().includes("/api/v1/login") && response.status() === 200, { timeout: 60000 }, ), - page.getByRole("button", { name: "Sign In" }).click(), + page.getByRole("button", { name: TEXTS.signIn }).click(), ]); // Verify admin can't see User A's flow diff --git a/src/frontend/tests/core/features/user-progress-track.spec.ts b/src/frontend/tests/core/features/user-progress-track.spec.ts index c3e4ade65c..d1da57fd4e 100644 --- a/src/frontend/tests/core/features/user-progress-track.spec.ts +++ b/src/frontend/tests/core/features/user-progress-track.spec.ts @@ -5,6 +5,7 @@ import { addNewUserAndLogin } from "../../utils/add-new-user-and-loggin"; import { cleanAllFlows } from "../../utils/clean-all-flows"; import { cleanOldFolders } from "../../utils/clean-old-folders"; +import { TEXTS } from "../../utils/constants/texts"; test( "admin user must be able to track their progress in getting started", { tag: ["@release", "@api"] }, @@ -81,7 +82,9 @@ async function progressTrackTestFn( await page.getByTestId("new_project_btn_empty_page").click(); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, diff --git a/src/frontend/tests/core/features/voice-assistant.spec.ts b/src/frontend/tests/core/features/voice-assistant.spec.ts index 2ff686c072..e016f6e9f4 100644 --- a/src/frontend/tests/core/features/voice-assistant.spec.ts +++ b/src/frontend/tests/core/features/voice-assistant.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; // TODO: Need to review the voice assistant vs text to voice test.skip( "should able to see and interact with voice assistant", @@ -29,7 +30,9 @@ test.skip( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.getByTestId("playground-btn-flow-io").click(); await expect(page.getByTestId("voice-button")).toBeVisible(); @@ -95,7 +98,9 @@ test.skip( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.getByTestId("playground-btn-flow-io").click(); await expect(page.getByTestId("voice-button")).not.toBeVisible(); @@ -123,7 +128,9 @@ test.skip( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.getByTestId("playground-btn-flow-io").click(); await expect(page.getByTestId("voice-button")).toBeVisible(); diff --git a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts index be3e3dad9d..445d596b94 100644 --- a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts +++ b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts @@ -1,43 +1,34 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { expect, test } from "../../fixtures"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Basic Prompting (Hello, World)", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Basic Prompting"); await initialGPTsetup(page); + await buildFlowAndWait(page); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); //create a new session - default session can not be deleted await page.getByTestId("new-chat").click(); - await page.getByTitle("New Session 0").isVisible(); - + await expect(page.getByTitle("New Session 0")).toBeVisible(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, }); @@ -57,16 +48,35 @@ withEventDeliveryModes( timeout: 100000, }); - await page.getByText("matey").last().isVisible(); + await expect(page.getByText("matey").last()).toBeVisible(); - await page.getByText("timestamp", { exact: true }).last().isVisible(); - await page.getByText("text", { exact: true }).last().isVisible(); - await page.getByText("sender", { exact: true }).last().isVisible(); - await page.getByText("sender_name", { exact: true }).last().isVisible(); - await page.getByText("session_id", { exact: true }).last().isVisible(); - await page.getByText("files", { exact: true }).last().isVisible(); + // Open the message logs table view to verify metadata columns + // (timestamp/text/sender/...). The header chat-menu is hidden in + // fullscreen, so click the sidebar session more-menu instead. + await page + .locator('[data-testid^="session-"][data-testid$="-more-menu"]') + .last() + .click(); + await page.getByTestId("message-logs-option").click(); - await page.getByRole("gridcell").last().isVisible(); + await expect( + page.getByText("timestamp", { exact: true }).last(), + ).toBeVisible(); + await expect(page.getByText("text", { exact: true }).last()).toBeVisible(); + await expect( + page.getByText("sender", { exact: true }).last(), + ).toBeVisible(); + await expect( + page.getByText("sender_name", { exact: true }).last(), + ).toBeVisible(); + await expect( + page.getByText("session_id", { exact: true }).last(), + ).toBeVisible(); + await expect(page.getByText("files", { exact: true }).last()).toBeVisible(); + await expect(page.getByRole("gridcell").last()).toBeVisible(); + + // Close the logs panel so the rest of the playground UI is reachable again. + await page.getByRole("button", { name: TEXTS.close }).click(); // Use sidebar session more menu (chat-header-more-menu is hidden in fullscreen) await page .locator('[data-testid^="session-"][data-testid$="-more-menu"]') @@ -77,6 +87,8 @@ withEventDeliveryModes( timeout: 100000, }); - await page.getByTestId("input-chat-playground").last().isVisible(); + await expect( + page.getByTestId("input-chat-playground").last(), + ).toBeVisible(); }, ); diff --git a/src/frontend/tests/core/integrations/Blog Writer.spec.ts b/src/frontend/tests/core/integrations/Blog Writer.spec.ts index ab45b2e6be..bdea21cb44 100644 --- a/src/frontend/tests/core/integrations/Blog Writer.spec.ts +++ b/src/frontend/tests/core/integrations/Blog Writer.spec.ts @@ -1,27 +1,18 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { expect, test } from "../../fixtures"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Blog Writer", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Blog Writer" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Blog Writer"); await initialGPTsetup(page); @@ -51,11 +42,13 @@ withEventDeliveryModes( await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page .getByPlaceholder( "No chat input variables found. Click to run your flow.", @@ -64,8 +57,8 @@ withEventDeliveryModes( .last() .isVisible(); - await page.getByText("turtles").last().isVisible(); - await page.getByText("sea").last().isVisible(); - await page.getByText("survival").last().isVisible(); + await expect(page.getByText("turtles").last()).toBeVisible(); + await expect(page.getByText("sea").last()).toBeVisible(); + await expect(page.getByText("survival").last()).toBeVisible(); }, ); diff --git a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts index aed2d1c194..b9a9d4b58b 100644 --- a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts @@ -1,11 +1,11 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { selectAnthropicModel } from "../../utils/select-anthropic-model"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Custom Component Generator", { tag: ["@release", "@starter-projects"] }, @@ -14,11 +14,7 @@ withEventDeliveryModes( !process?.env?.ANTHROPIC_API_KEY, "ANTHROPIC_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); await awaitBootstrapTest(page); @@ -44,7 +40,7 @@ withEventDeliveryModes( await page.waitForTimeout(1000); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "hidden", timeout: 30000 * 3 }); const textContents = await getAllResponseMessage(page); diff --git a/src/frontend/tests/core/integrations/Document QA.spec.ts b/src/frontend/tests/core/integrations/Document QA.spec.ts index 821e4560f0..8eff70bac4 100644 --- a/src/frontend/tests/core/integrations/Document QA.spec.ts +++ b/src/frontend/tests/core/integrations/Document QA.spec.ts @@ -1,28 +1,20 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { uploadFile } from "../../utils/upload-file"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Document Q&A", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Document Q&A" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Document Q&A"); await initialGPTsetup(page); await uploadFile(page, "test_file.txt"); @@ -30,13 +22,13 @@ withEventDeliveryModes( await page.waitForSelector('[data-testid="button_run_chat output"]', { timeout: 3000, }); + await buildFlowAndWait(page); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); @@ -56,7 +48,7 @@ withEventDeliveryModes( timeout: 10000, }); - await page.getByText("this is a test file").last().isVisible(); - expect(await page.getByTestId("div-chat-message).last().count()).toBe(1)")); + await expect(page.getByText("this is a test file").last()).toBeVisible(); + expect(await page.getByTestId("div-chat-message").last().count()).toBe(1); }, ); diff --git a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts b/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts deleted file mode 100644 index 1b0e277fab..0000000000 --- a/src/frontend/tests/core/integrations/Dynamic Agent.spec.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; - -test.skip( - "Dynamic Agent", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - test.skip( - !process?.env?.SEARCH_API_KEY, - "SEARCH_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Dynamic Agent" }).last().click(); - await initialGPTsetup(page); - - await page - .getByTestId("popover-anchor-input-api_key") - .last() - .fill(process.env.SEARCH_API_KEY ?? ""); - - await page - .getByTestId("textarea_str_input_value") - .first() - .fill("how much is an apple stock today"); - await page.getByTestId("button_run_chat output").click(); - - await page.waitForSelector("text=built successfully", { - timeout: 60000 * 3, - }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.waitForTimeout(1000); - expect(page.getByText("apple").last()).toBeVisible(); - const textContents = await page - .getByTestId("div-chat-message") - .allTextContents(); - const concatAllText = textContents.join(" "); - expect(concatAllText.toLocaleLowerCase()).toContain("apple"); - expect(concatAllText.toLocaleLowerCase()).not.toContain("error"); - expect(concatAllText.toLocaleLowerCase()).not.toContain("apologize"); - const allTextLength = concatAllText.length; - expect(allTextLength).toBeGreaterThan(100); - }, -); diff --git a/src/frontend/tests/core/integrations/Financial Report Parser.spec.ts b/src/frontend/tests/core/integrations/Financial Report Parser.spec.ts deleted file mode 100644 index d8a9358fdb..0000000000 --- a/src/frontend/tests/core/integrations/Financial Report Parser.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; -import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; - -withEventDeliveryModes( - "Financial Report Parser", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Financial Report Parser" }) - .click(); - - await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { - timeout: 100000, - }); - - await initialGPTsetup(page); - - await page.getByText("Parser", { exact: true }).last().click(); - - await page.getByTestId("tab_1_stringify").click(); - - await page.getByTestId("playground-btn-flow-io").click(); - - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 3000, - }); - - await page.getByTestId("button-send").click(); - - try { - // Wait for the flow building indicator to appear and then disappear - await page.waitForSelector('[data-testid="stop_building_button"]', { - timeout: 30000, - state: "visible", - }); - await page.waitForSelector('[data-testid="stop_building_button"]', { - timeout: 180000, - state: "hidden", - }); - } catch (_error) { - console.error("Timeout error"); - test.skip(true, "Timeout error"); - } - - // Wait for the chat response to appear - await page.waitForSelector('[data-testid="div-chat-message"]', { - timeout: 30000, - }); - const textContents = await page - .getByTestId("div-chat-message") - .last() - .allTextContents(); - const concatAllText = textContents.join(" ").toLowerCase(); - expect(concatAllText.length).toBeGreaterThan(10); - expect(concatAllText).toContain("ebitda"); - }, -); diff --git a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts b/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts deleted file mode 100644 index 6698a57362..0000000000 --- a/src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; - -test.skip( - "Hierarchical Tasks Agent", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - !process?.env?.SEARCH_API_KEY, - "SEARCH_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Hierarchical Tasks Agent" }) - .first() - .click(); - await initialGPTsetup(page); - - await page - .getByTestId("popover-anchor-input-api_key") - .last() - .fill(process.env.SEARCH_API_KEY ?? ""); - - await page.waitForTimeout(1000); - - await page.getByTestId("button_run_chat output").click(); - - await page.waitForTimeout(1000); - - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); - - expect(await page.locator(".markdown").count()).toBeGreaterThan(0); - - expect(await page.getByText("Langflow").count()).toBeGreaterThan(2); - }, -); diff --git a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts b/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts deleted file mode 100644 index effdff30aa..0000000000 --- a/src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { getAllResponseMessage } from "../../utils/get-all-response-message"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; -import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; - -withEventDeliveryModes( - "Image Sentiment Analysis", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByText("Image Sentiment Analysis", { exact: true }) - .last() - .click(); - - await initialGPTsetup(page); - - //* TODO: Remove these 3 steps once the template is updated *// - await page - .getByTestId("handle-structuredoutput-shownode-structured output-right") - .click(); - - await page.getByTestId("handle-parser-shownode-json or table-left").click(); - await page.getByTestId("tab_1_stringify").click(); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); - - await page.waitForSelector('[data-testid="input-chat-playground"]', { - timeout: 100000, - }); - - // Upload image using the hidden file input - const filePath = path.resolve(__dirname, "../../assets/chain.png"); - const fileInput = page.locator('input[type="file"]'); - await fileInput.setInputFiles(filePath); - - // Wait for file preview to appear (shows loading then the image) - await page.waitForSelector('img[alt="chain.png"]', { timeout: 30000 }); - - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 100000, - }); - - await page.getByTestId("button-send").click(); - - // Wait for the flow to complete - try { - await page.waitForSelector('[data-testid="stop_building_button"]', { - timeout: 30000, - state: "visible", - }); - await page.waitForSelector('[data-testid="stop_building_button"]', { - timeout: 180000, - state: "hidden", - }); - } catch (_error) { - console.error("Timeout error"); - test.skip(true, "Timeout error"); - } - - // Verify the image is visible in the chat messages after sending - // Note: Server renames file with timestamp prefix (e.g., "2026-02-03_13-55-02_chain.png") - await expect(page.locator('img[alt$="chain.png"]')).toBeVisible(); - - await page.waitForSelector('[data-testid="div-chat-message"]', { - timeout: 30000, - }); - - const textContents = await getAllResponseMessage(page); - expect(textContents.length).toBeGreaterThan(10); - expect(textContents.toLowerCase()).toContain("sentiment"); - expect(textContents.toLowerCase()).toContain("neutral"); - expect(textContents.toLowerCase()).toContain("description"); - }, -); diff --git a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts index 6595dae9db..d1f14cd484 100644 --- a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts +++ b/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts @@ -1,36 +1,26 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { unselectNodes } from "../../utils/unselect-nodes"; import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; test( "Instagram Copywriter", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - + skipIfMissing.openAiKey(); test.skip( !process?.env?.TAVILY_API_KEY, "TAVILY_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Instagram Copywriter" }).click(); + await openStarterProject(page, "Instagram Copywriter"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -57,11 +47,13 @@ test( await unselectNodes(page); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 2, }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page .getByText("Create a Langflow post", { exact: true }) .last() diff --git a/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts b/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts index 4435fde626..b446c4cccb 100644 --- a/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts +++ b/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts @@ -1,10 +1,10 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; test( "Invoice Summarizer", { tag: ["@release", "@starter-projects"] }, @@ -15,15 +15,8 @@ test( !process?.env?.NEEDLE_COLLECTION_ID, "OPENAI_API_KEY, NEEDLE_API_KEY, and NEEDLE_COLLECTION_ID required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Invoice Summarizer" }).click(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Invoice Summarizer"); await initialGPTsetup(page); @@ -53,14 +46,18 @@ test( await page.getByTestId("button_run_chat output").click(); // Wait for the flow to build successfully - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 120000, + }); // Switch to Playground - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); // Wait for the playground to be ready const inputPlaceholder = page - .getByPlaceholder("Send a message...", { exact: true }) + .getByPlaceholder(TEXTS.placeholderSendMessage, { exact: true }) .last(); await expect(inputPlaceholder).toBeVisible({ timeout: 10000 }); diff --git a/src/frontend/tests/core/integrations/Market Research.spec.ts b/src/frontend/tests/core/integrations/Market Research.spec.ts index 09b6d54fdf..98a630e572 100644 --- a/src/frontend/tests/core/integrations/Market Research.spec.ts +++ b/src/frontend/tests/core/integrations/Market Research.spec.ts @@ -1,36 +1,27 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { disableInspectPanel } from "../../utils/open-advanced-options"; import { unselectNodes } from "../../utils/unselect-nodes"; import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Market Research", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); + skipIfMissing.openAiKey(); test.skip( !process?.env?.TAVILY_API_KEY, "TAVILY_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Market Research" }).click(); + await openStarterProject(page, "Market Research"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -58,13 +49,15 @@ withEventDeliveryModes( await page.getByTestId("tab_1_stringify").click(); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 60000 * 3, }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts index 48469eedb6..f356989c70 100644 --- a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts +++ b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts @@ -1,36 +1,28 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Memory Chatbot", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Memory Chatbot" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Memory Chatbot"); await initialGPTsetup(page); - - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await buildFlowAndWait(page); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/News Aggregator.spec.ts b/src/frontend/tests/core/integrations/News Aggregator.spec.ts index aef05e3db4..7078a58631 100644 --- a/src/frontend/tests/core/integrations/News Aggregator.spec.ts +++ b/src/frontend/tests/core/integrations/News Aggregator.spec.ts @@ -1,9 +1,9 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; withEventDeliveryModes( "News Aggregator", @@ -13,21 +13,10 @@ withEventDeliveryModes( !process?.env?.AGENTQL_API_KEY, "AGENTQL_API_KEY required to run this test", ); - - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "News Aggregator" }).click(); + await openStarterProject(page, "News Aggregator"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, diff --git a/src/frontend/tests/core/integrations/Pokedex Agent.spec.ts b/src/frontend/tests/core/integrations/Pokedex Agent.spec.ts index cdd88d3476..2036933767 100644 --- a/src/frontend/tests/core/integrations/Pokedex Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Pokedex Agent.spec.ts @@ -1,33 +1,24 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Pokedex Agent", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Pokédex Agent" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Pokédex Agent"); await initialGPTsetup(page); await page.getByTestId("playground-btn-flow-io").click(); - await page.getByTestId("input-chat-playground").isVisible(); + await expect(page.getByTestId("input-chat-playground")).toBeVisible(); await page.getByTestId("input-chat-playground").click(); await page .getByTestId("input-chat-playground") @@ -35,7 +26,7 @@ withEventDeliveryModes( await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 40000 }); if (await stopButton.isVisible()) { diff --git a/src/frontend/tests/core/integrations/Portfolio Website Code Generator.spec.ts b/src/frontend/tests/core/integrations/Portfolio Website Code Generator.spec.ts deleted file mode 100644 index 6bc0fd057c..0000000000 --- a/src/frontend/tests/core/integrations/Portfolio Website Code Generator.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; -import { uploadFile } from "../../utils/upload-file"; -import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; - -withEventDeliveryModes( - "Portfolio Website Code Generator", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.ANTHROPIC_API_KEY, - "ANTHROPIC_API_KEY required to run this test", - ); - // TODO: remove this skip once the test is stabilized - test.skip(true, "Skipping flaky test until it can be stabilized"); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Portfolio Website Code Generator" }) - .click(); - - await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { - timeout: 100000, - }); - - await initialGPTsetup(page, { - skipAdjustScreenView: true, - skipSelectGptModel: true, - }); - - await page - .getByTestId("popover-anchor-input-api_key") - .last() - .fill(process.env.ANTHROPIC_API_KEY ?? ""); - - await page - .getByTestId("popover-anchor-input-api_key") - .first() - .fill(process.env.ANTHROPIC_API_KEY ?? ""); - - await uploadFile(page, "resume.txt"); - - await page.getByTestId("playground-btn-flow-io").click(); - - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 3000, - }); - - await page.getByTestId("button-send").click(); - - await page.waitForSelector('[data-testid="chat-code-tab"]', { - timeout: 30000 * 3, - }); - - await page.waitForSelector(".markdown", { timeout: 30000 }); - - const textContents = await page - .locator(".markdown") - .last() - .allTextContents(); - - const concatAllText = textContents.join(" ").toLowerCase(); - - expect(concatAllText.length).toBeGreaterThan(200); - - expect(concatAllText).toContain("div"); - expect(concatAllText).toContain("body"); - }, -); diff --git a/src/frontend/tests/core/integrations/Price Deal Finder.spec.ts b/src/frontend/tests/core/integrations/Price Deal Finder.spec.ts deleted file mode 100644 index a2cfe4b23f..0000000000 --- a/src/frontend/tests/core/integrations/Price Deal Finder.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; -import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; - -withEventDeliveryModes( - "Price Deal Finder", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.AGENTQL_API_KEY, - "AGENTQL_API_KEY required to run this test", - ); - - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - test.skip( - !process?.env?.TAVILY_API_KEY, - "TAVILY_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Price Deal Finder" }).click(); - await adjustScreenView(page); - - await initialGPTsetup(page, { - skipAdjustScreenView: true, - skipAddNewApiKeys: true, - skipSelectGptModel: true, - }); - - await page - .getByTestId("popover-anchor-input-api_key") - .nth(0) - .fill(process?.env?.TAVILY_API_KEY ?? ""); - - await page - .getByTestId("popover-anchor-input-api_key") - .nth(1) - .fill(process?.env?.AGENTQL_API_KEY ?? ""); - - await page - .getByTestId("popover-anchor-input-api_key") - .nth(2) - .fill(process?.env?.OPENAI_API_KEY ?? ""); - - await page.getByTestId("playground-btn-flow-io").click(); - - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 3000, - }); - - const product = randomProduct(); - - await page.getByTestId("input-chat-playground").fill(product); - - await page.getByTestId("button-send").click(); - - try { - await page.waitForSelector('[data-testid="button-stop"]', { - timeout: 180000, - state: "hidden", - }); - } catch (_error) { - console.error("Timeout error"); - test.skip(true, "Timeout error"); - } - - await page.waitForSelector(".markdown", { timeout: 3000 }); - - const textContents = await page - .locator(".markdown") - .last() - .allTextContents(); - - const concatAllText = textContents.join(" ").toLowerCase(); - - expect(concatAllText.length).toBeGreaterThan(100); - - const zeldaChapter = product.split(" ")[1]; - expect(concatAllText).toContain(zeldaChapter); - }, -); - -const randomProduct = () => { - const products = [ - "Zelda Tears of the Kingdom", - "Zelda Ocarina of Time", - "Zelda Majora's Mask", - "Zelda The Wind Waker", - "Zelda Twilight Princess", - "Zelda Skyward Sword", - "Zelda Breath of the Wild", - "Zelda Link's Awakening", - "Zelda Link to the Past", - ]; - - return products[Math.floor(Math.random() * products.length)].toLowerCase(); -}; diff --git a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts index f2d3311bcb..02e91707ca 100644 --- a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts +++ b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts @@ -1,30 +1,21 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { waitForOpenModalWithChatInput } from "../../utils/wait-for-open-modal"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Prompt Chaining", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Prompt Chaining" }).click(); + await openStarterProject(page, "Prompt Chaining"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -33,11 +24,15 @@ withEventDeliveryModes( await initialGPTsetup(page); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 60000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 60000, + }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/Research Translation Loop.spec.ts b/src/frontend/tests/core/integrations/Research Translation Loop.spec.ts index c6085a4c52..c61c05139b 100644 --- a/src/frontend/tests/core/integrations/Research Translation Loop.spec.ts +++ b/src/frontend/tests/core/integrations/Research Translation Loop.spec.ts @@ -1,24 +1,18 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { selectGptModel } from "../../utils/select-gpt-model"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Research Translation Loop.spec", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); @@ -50,7 +44,7 @@ withEventDeliveryModes( await page.getByTestId("button-send").click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 40000 }); if (await stopButton.isVisible()) { diff --git a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts index 19effda577..70e9f66ea6 100644 --- a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts +++ b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts @@ -1,43 +1,35 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-modal"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "SEO Keyword Generator", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "SEO Keyword Generator" }).click(); + await openStarterProject(page, "SEO Keyword Generator"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); await initialGPTsetup(page); + await buildFlowAndWait(page); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts index 28a1853ac5..63d113eb30 100644 --- a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts +++ b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts @@ -1,43 +1,35 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-modal"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "SaaS Pricing", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "SaaS Pricing" }).click(); + await openStarterProject(page, "SaaS Pricing"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); await initialGPTsetup(page); + await buildFlowAndWait(page); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts b/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts deleted file mode 100644 index 5c09b4d0c2..0000000000 --- a/src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { initialGPTsetup } from "../../utils/initialGPTsetup"; - -test.skip( - "Sequential Task Agent", - { tag: ["@release", "@starter-projects"] }, - async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: "Sequential Tasks Agent" }) - .first() - .click(); - - await initialGPTsetup(page); - - await page.getByTestId("button_run_chat output").click(); - - await page.waitForTimeout(1000); - - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); - - expect(await page.locator(".markdown").count()).toBeGreaterThan(0); - - expect(await page.getByText("Agile").count()).toBeGreaterThan(0); - }, -); diff --git a/src/frontend/tests/core/integrations/Simple Agent Memory.spec.ts b/src/frontend/tests/core/integrations/Simple Agent Memory.spec.ts index 2f2f64309d..63f3e3ec7f 100644 --- a/src/frontend/tests/core/integrations/Simple Agent Memory.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent Memory.spec.ts @@ -1,28 +1,25 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Simple Agent Memory", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); // Open Simple Agent template await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await initialGPTsetup(page); // Open Playground diff --git a/src/frontend/tests/core/integrations/Simple Agent.spec.ts b/src/frontend/tests/core/integrations/Simple Agent.spec.ts index e4afd32f7c..5accea0e82 100644 --- a/src/frontend/tests/core/integrations/Simple Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent.spec.ts @@ -1,28 +1,18 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Simple Agent", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Simple Agent"); await initialGPTsetup(page); await page.getByTestId("playground-btn-flow-io").click(); @@ -34,7 +24,7 @@ withEventDeliveryModes( await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 30000 }); if (await stopButton.isVisible()) { diff --git a/src/frontend/tests/core/integrations/Social Media Agent.spec.ts b/src/frontend/tests/core/integrations/Social Media Agent.spec.ts index 7fbbce71f6..71fbf68ed1 100644 --- a/src/frontend/tests/core/integrations/Social Media Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Social Media Agent.spec.ts @@ -1,10 +1,10 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; function getRandomSocialMediaQuery(): string { const companies = [ "OpenAI", @@ -61,15 +61,8 @@ withEventDeliveryModes( !process?.env?.APIFY_API_TOKEN, "APIFY_API_TOKEN required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Social Media Agent" }).click(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Social Media Agent"); await initialGPTsetup(page); @@ -98,7 +91,7 @@ withEventDeliveryModes( await page.getByTestId("button-send").last().click(); - const stopButton = page.getByRole("button", { name: "Stop" }); + const stopButton = page.getByRole("button", { name: TEXTS.stop }); await stopButton.waitFor({ state: "visible", timeout: 30000 }); if (await stopButton.isVisible()) { diff --git a/src/frontend/tests/core/integrations/Text Sentiment Analysis.spec.ts b/src/frontend/tests/core/integrations/Text Sentiment Analysis.spec.ts index c222d08509..a10f29c176 100644 --- a/src/frontend/tests/core/integrations/Text Sentiment Analysis.spec.ts +++ b/src/frontend/tests/core/integrations/Text Sentiment Analysis.spec.ts @@ -1,25 +1,19 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { unselectNodes } from "../../utils/unselect-nodes"; import { uploadFile } from "../../utils/upload-file"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "user should be able to analyze text sentiment", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); @@ -39,9 +33,13 @@ withEventDeliveryModes( await page.getByText("Expand").click(); await unselectNodes(page); await page.getByTestId("button_run_chat output").last().click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 120000, + }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page .getByText("Add a Chat Input component to your flow to send messages.", { exact: true, diff --git a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts index 682f2c00fe..ba05ed4f47 100644 --- a/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Travel Planning Agent.spec.ts @@ -1,29 +1,22 @@ import { type Page } from "@playwright/test"; -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Travel Planning Agent", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - + skipIfMissing.openAiKey(); test.skip( !process?.env?.SEARCH_API_KEY, "SEARCH_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); await awaitBootstrapTest(page); @@ -64,11 +57,13 @@ withEventDeliveryModes( test.skip(); } - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 60000 * 3, }); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector("text=default session", { timeout: 30000, diff --git a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts index a53ec0fcd4..92005aedcc 100644 --- a/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Twitter Thread Generator.spec.ts @@ -1,25 +1,20 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { getAllResponseMessage } from "../../utils/get-all-response-message"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { waitForOpenModalWithoutChatInput } from "../../utils/wait-for-open-modal"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { buildFlowAndWait } from "../../utils/flow/build-flow-and-wait"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "Twitter Thread Generator", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); await awaitBootstrapTest(page); @@ -37,13 +32,13 @@ withEventDeliveryModes( await page.getByTestId("title-Chat Output").click(); await page.getByTestId("icon-MoreHorizontal").click(); await page.getByText("Expand").click(); + await buildFlowAndWait(page); - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); - - await page.getByRole("button", { name: "Playground", exact: true }).click(); await page - .getByText("No input message provided.", { exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); + await page + .getByText(TEXTS.labelNoInputMessage, { exact: true }) .last() .isVisible(); diff --git a/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts b/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts index 78ed65f7e8..04e891d344 100644 --- a/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts +++ b/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts @@ -1,33 +1,23 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; +import { TEXTS } from "../../utils/constants/texts"; withEventDeliveryModes( "YouTube Analysis", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - + skipIfMissing.openAiKey(); test.skip( !process?.env?.YOUTUBE_API_KEY, "YOUTUBE_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "YouTube Analysis" }).click(); + await openStarterProject(page, "YouTube Analysis"); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -42,7 +32,9 @@ withEventDeliveryModes( await page.getByTestId("button_run_chat output").last().click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 120000, + }); await page.getByTestId("playground-btn-flow-io").click(); diff --git a/src/frontend/tests/core/integrations/assistant-panel-integration.spec.ts b/src/frontend/tests/core/integrations/assistant-panel-integration.spec.ts index a9a5d09ed3..98423d7a2b 100644 --- a/src/frontend/tests/core/integrations/assistant-panel-integration.spec.ts +++ b/src/frontend/tests/core/integrations/assistant-panel-integration.spec.ts @@ -1,24 +1,14 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { selectAssistantOpenAiModel } from "../../utils/select-assistant-openai-model"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; test.describe("Assistant Panel Integration", { tag: ["@release"] }, () => { test.beforeEach(async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openStarterProject(page, "Basic Prompting"); // Open assistant panel and wait for model selector await page.getByTestId("assistant-button").click(); diff --git a/src/frontend/tests/core/integrations/assistant-panel.spec.ts b/src/frontend/tests/core/integrations/assistant-panel.spec.ts index f7f35deaaa..87bde3d64f 100644 --- a/src/frontend/tests/core/integrations/assistant-panel.spec.ts +++ b/src/frontend/tests/core/integrations/assistant-panel.spec.ts @@ -1,17 +1,11 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; test.describe("Assistant Panel UI", { tag: ["@release"] }, () => { test("should open and close from canvas controls", async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + skipIfMissing.openAiKey(); + await openStarterProject(page, "Basic Prompting"); // Panel should not be visible initially await expect(page.getByTestId("assistant-panel")).not.toBeVisible(); diff --git a/src/frontend/tests/core/integrations/decisionFlow.spec.ts b/src/frontend/tests/core/integrations/decisionFlow.spec.ts index 6908f9a251..3a5c6603f4 100644 --- a/src/frontend/tests/core/integrations/decisionFlow.spec.ts +++ b/src/frontend/tests/core/integrations/decisionFlow.spec.ts @@ -11,6 +11,7 @@ import { import { selectGptModel } from "../../utils/select-gpt-model"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "should create a flow with decision", { tag: ["@release", "@components", "@workflow"] }, @@ -34,7 +35,7 @@ test( //---------------------------------- CHAT INPUT await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatInput); await page.waitForSelector('[data-testid="input_outputChat Input"]', { timeout: 2000, }); @@ -157,7 +158,7 @@ test( }); //---------------------------------- PROMPT await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', { @@ -172,7 +173,9 @@ test( //---------------------------------- OPENAI await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.providerOpenAiSearch); await page.waitForSelector('[data-testid="openai_openai_draggable"]', { timeout: 2000, }); @@ -195,7 +198,7 @@ test( }); //---------------------------------- CHAT OUTPUT await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 2000, }); @@ -209,7 +212,7 @@ test( }); //---------------------------------- CHAT OUTPUT await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 2000, }); @@ -244,7 +247,7 @@ test( User: {user_message} AI: `); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); //---------------------------------- MAKE CONNECTIONS await page .getByTestId("handle-createlist-shownode-json list-right") @@ -370,7 +373,9 @@ test( await adjustScreenView(page); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, }); diff --git a/src/frontend/tests/core/integrations/similarity.spec.ts b/src/frontend/tests/core/integrations/similarity.spec.ts index f71eacd129..e96f63ba19 100644 --- a/src/frontend/tests/core/integrations/similarity.spec.ts +++ b/src/frontend/tests/core/integrations/similarity.spec.ts @@ -7,6 +7,7 @@ import { unselectNodes } from "../../utils/unselect-nodes"; import { updateOldComponents } from "../../utils/update-old-components"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to check similarity between embedding texts", { tag: ["@release", "@components"] }, @@ -89,7 +90,7 @@ test( //seventh component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page.waitForSelector("text=Text Output", { timeout: 1000, }); @@ -128,7 +129,7 @@ test( await page .getByTestId("popover-anchor-input-message") .first() - .fill("langflow"); + .fill(TEXTS.authDefaultCredential); const firstApiKeyInput = page .getByTestId("popover-anchor-input-openai_api_key") @@ -249,7 +250,9 @@ test( await page.getByTestId("button_run_text output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 120000, + }); await unselectNodes(page); diff --git a/src/frontend/tests/core/integrations/textInputOutput.spec.ts b/src/frontend/tests/core/integrations/textInputOutput.spec.ts deleted file mode 100644 index 58fd1cb843..0000000000 --- a/src/frontend/tests/core/integrations/textInputOutput.spec.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { selectGptModel } from "../../utils/select-gpt-model"; - -test.skip( - "TextInputOutputComponent", - { tag: ["@release", "@components"] }, - async ({ page }) => { - // commented out because new playground does not support text io yet - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); - await page.waitForTimeout(1000); - await page - .getByTestId("input_outputText Input") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); - await page.waitForTimeout(1000); - await page - .getByTestId("openaiOpenAI") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); - await adjustScreenView(page); - let visibleElementHandle; - const elementsTextInputOutput = await page - .getByTestId("handle-textinput-shownode-output text-right") - .all(); - for (const element of elementsTextInputOutput) { - if (await element.isVisible()) { - visibleElementHandle = element; - break; - } - } - await visibleElementHandle!.waitFor({ - state: "visible", - timeout: 30000, - }); - await visibleElementHandle!.hover(); - await page.mouse.down(); - for (const element of elementsTextInputOutput) { - if (await element.isVisible()) { - visibleElementHandle = element; - break; - } - } - await visibleElementHandle!.waitFor({ - state: "visible", - timeout: 30000, - }); - // Move to the second element - await visibleElementHandle!.hover(); - // Release the mouse - await page.mouse.up(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); - await page - .getByTestId("input_outputText Output") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - await page.mouse.up(); - await page.mouse.down(); - await adjustScreenView(page, { numberOfZoomOut: 6 }); - - const elementsOpenAiOutput = await page - .getByTestId("handle-openaimodel-shownode-text-right") - .all(); - for (const element of elementsOpenAiOutput) { - if (await element.isVisible()) { - visibleElementHandle = element; - break; - } - } - await visibleElementHandle!.waitFor({ - state: "visible", - timeout: 30000, - }); - // Click and hold on the first element - await visibleElementHandle!.hover(); - await page.mouse.down(); - const elementTextOutputInput = await page - .getByTestId("handle-textoutput-shownode-inputs-left") - .all(); - for (const element of elementTextOutputInput) { - if (await element.isVisible()) { - visibleElementHandle = element; - break; - } - } - await visibleElementHandle!.waitFor({ - state: "visible", - timeout: 30000, - }); - // Move to the second element - await visibleElementHandle!.hover(); - // Release the mouse - await page.mouse.up(); - await page - .getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/) - .getByTestId("textarea_str_input_value") - .fill("This is a test!"); - let outdatedComponents = await page.getByTestId("update-button").count(); - while (outdatedComponents > 0) { - await page.getByTestId("update-button").first().click(); - await page.waitForTimeout(1000); - outdatedComponents = await page.getByTestId("update-button").count(); - } - let filledApiKey = await page.getByTestId("remove-icon-badge").count(); - while (filledApiKey > 0) { - await page.getByTestId("remove-icon-badge").first().click(); - await page.waitForTimeout(1000); - filledApiKey = await page.getByTestId("remove-icon-badge").count(); - } - const apiKeyInput = page.getByTestId("popover-anchor-input-api_key"); - const isApiKeyInputVisible = await apiKeyInput.isVisible(); - if (isApiKeyInputVisible) { - await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? ""); - } - await selectGptModel(page); - - await page.waitForTimeout(1000); - await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.getByTestId("button_run_text_output").click(); - await page - .getByTestId(/^rf__node-TextOutput-[a-zA-Z0-9]+$/) - .getByTestId("output-inspection-output message-chatoutput") - .click(); - await page.getByText("Run Flow", { exact: true }).click(); - await page.waitForTimeout(5000); - let textInputContent = await page - .getByPlaceholder("Enter text...") - .textContent(); - expect(textInputContent).toBe("This is a test!"); - await page.getByText("Outputs", { exact: true }).nth(1).click(); - await page.getByText("Text Output", { exact: true }).nth(2).click(); - let contentOutput = await page - .getByPlaceholder("Enter text...") - .inputValue(); - expect(contentOutput).not.toBe(null); - await page.keyboard.press("Escape"); - await page - .getByTestId(/^rf__node-TextInput-[a-zA-Z0-9]+$/) - .getByTestId("textarea_str_input_value") - .fill("This is a test, again just to be sure!"); - await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.getByText("Run Flow", { exact: true }).click(); - await page.waitForTimeout(5000); - textInputContent = await page - .getByPlaceholder("Enter text...") - .textContent(); - expect(textInputContent).toBe("This is a test, again just to be sure!"); - await page.getByText("Outputs", { exact: true }).nth(1).click(); - await page.getByText("Text Output", { exact: true }).nth(2).click(); - contentOutput = await page.getByPlaceholder("Enter text...").inputValue(); - expect(contentOutput).not.toBe(null); - }, -); diff --git a/src/frontend/tests/core/regression/general-bugs-remove-session-after-logout.spec.ts b/src/frontend/tests/core/regression/general-bugs-remove-session-after-logout.spec.ts index 692642dde3..10d9bcf85b 100644 --- a/src/frontend/tests/core/regression/general-bugs-remove-session-after-logout.spec.ts +++ b/src/frontend/tests/core/regression/general-bugs-remove-session-after-logout.spec.ts @@ -1,5 +1,6 @@ import { expect, test } from "../../fixtures"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must not be able to login after logout and refresh the page when auto_login is false", { tag: ["@release", "@api"] }, @@ -30,16 +31,22 @@ test( await page.goto("/"); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); await page.waitForSelector('[data-testid="mainpage_title"]', { timeout: 30000, @@ -51,13 +58,15 @@ test( sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); await page.waitForTimeout(1000); await page.reload(); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); const isLoggedIn = await page .getByTestId("mainpage_title") diff --git a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts index f9a63f286e..017bc3db7b 100644 --- a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts @@ -1,25 +1,22 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to edit an empty prompt", { tag: ["@release", "@starter-projects"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -39,7 +36,7 @@ test( await page.keyboard.press(`ControlOrMeta+a`); await page.keyboard.press("Backspace"); - await page.getByText("Edit Prompt", { exact: true }).click(); + await page.getByText(TEXTS.editPrompt, { exact: true }).click(); await page.getByTestId("edit-prompt-sanitized").last().click(); @@ -47,7 +44,7 @@ test( .getByTestId("modal-promptarea_prompt_template") .fill("THIS IS A TEST"); - await page.getByText("Edit Prompt", { exact: true }).click(); + await page.getByText(TEXTS.editPrompt, { exact: true }).click(); let promptSanitizedText = await page .getByTestId("edit-prompt-sanitized") @@ -61,7 +58,7 @@ test( await page.keyboard.press(`ControlOrMeta+a`); await page.keyboard.press("Backspace"); - await page.getByText("Edit Prompt", { exact: true }).click(); + await page.getByText(TEXTS.editPrompt, { exact: true }).click(); await page.getByTestId("edit-prompt-sanitized").last().click(); @@ -69,7 +66,7 @@ test( .getByTestId("modal-promptarea_prompt_template") .fill("THIS IS A TEST 2"); - await page.getByText("Edit Prompt", { exact: true }).click(); + await page.getByText(TEXTS.editPrompt, { exact: true }).click(); promptSanitizedText = await page .getByTestId("edit-prompt-sanitized") diff --git a/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts index f41e05750a..7b40e7cb37 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-4.spec.ts @@ -3,6 +3,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "should be able to move flow from folder, rename it and be displayed on correct folder", { tag: ["@release"] }, @@ -34,21 +35,21 @@ test( await page.getByTestId("add-project-button").click(); - let countFolders = await page.getByText("New Project").count(); + let countFolders = await page.getByText(TEXTS.labelNewProject).count(); while (countFolders > 1) { - await page.getByText("New Project").first().hover(); + await page.getByText(TEXTS.labelNewProject).first().hover(); await page.getByTestId("more-options-button").first().click(); await page.getByTestId("btn-delete-project").click(); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); countFolders--; await page.waitForTimeout(1000); } // Get the bounding boxes of the elements const sourceElement = await page.getByTestId(`card-${randomName}`).first(); - const targetElement = await page.getByText("New Project").last(); + const targetElement = await page.getByText(TEXTS.labelNewProject).last(); const sourceBox = await sourceElement.boundingBox(); const targetBox = await targetElement.boundingBox(); @@ -67,7 +68,7 @@ test( await page.waitForTimeout(3000); - await page.getByText("New Project").last().click(); + await page.getByText(TEXTS.labelNewProject).last().click(); expect(await page.getByTestId(`card-${randomName}`).first().isVisible()); @@ -79,7 +80,7 @@ test( await page.waitForTimeout(3000); - await page.getByText("New Project").last().click(); + await page.getByText(TEXTS.labelNewProject).last().click(); expect( await page.getByTestId(`card-${secondRandomName}`).first().isVisible(), ); @@ -88,7 +89,9 @@ test( const secondSourceElement = await page .getByTestId(`card-${secondRandomName}`) .first(); - const secondTargetElement = await page.getByText("New Project").last(); + const secondTargetElement = await page + .getByText(TEXTS.labelNewProject) + .last(); const secondSourceBox = await secondSourceElement.boundingBox(); const secondTargetBox = await secondTargetElement.boundingBox(); diff --git a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts index dac10e8904..646ff1f1da 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts @@ -4,6 +4,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { zoomOut } from "../../utils/zoom-out"; +import { TEXTS } from "../../utils/constants/texts"; test( "should be able to see output preview from grouped components and connect components with a single click", { tag: ["@release", "@workspace", "@components"] }, @@ -19,7 +20,7 @@ test( await addLegacyComponents(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); await page.waitForSelector('[data-testid="input_outputText Input"]', { timeout: 3000, }); @@ -262,7 +263,9 @@ test( await page.getByTestId("button_run_text output").last().click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); expect( await page @@ -274,9 +277,10 @@ test( .first() .click(); - await page.getByText("Component Output").isVisible(); - - const text = await page.getByPlaceholder("Empty").textContent(); + await expect(page.getByText(TEXTS.componentOutput)).toBeVisible(); + const text = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .textContent(); const permutations = [ `${randomName}-${secondRandomName}-${thirdRandomName}`, diff --git a/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts b/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts index 08d436f791..5e666e4f10 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts @@ -1,27 +1,24 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { addLegacyComponents } from "../../utils/add-legacy-components"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to use chat memory as expected", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page); @@ -83,8 +80,8 @@ AI: await page.getByTestId("button_open_prompt_modal").nth(0).click(); await page.getByTestId("modal-promptarea_prompt_template").fill(prompt); - await page.getByText("Edit Prompt", { exact: true }).click(); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.editPrompt, { exact: true }).click(); + await page.getByText(TEXTS.checkAndSave).last().click(); await adjustScreenView(page); @@ -98,14 +95,16 @@ AI: await page.locator('//*[@id="react-flow-id"]').hover(); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="button-send"]', { timeout: 100000, }); await page - .getByPlaceholder("Send a message...") + .getByPlaceholder(TEXTS.placeholderSendMessage) .fill("hi, my car is blue and I like to eat pizza"); await page.getByTestId("button-send").click(); @@ -113,7 +112,7 @@ AI: await page.waitForSelector("text=AI", { timeout: 30000 }); await page - .getByPlaceholder("Send a message...") + .getByPlaceholder(TEXTS.placeholderSendMessage) .fill("what color is my car and what do I like to eat?"); await page.getByTestId("button-send").click(); diff --git a/src/frontend/tests/core/regression/ime-input-composition.spec.ts b/src/frontend/tests/core/regression/ime-input-composition.spec.ts index d3492829b8..ac6f599904 100644 --- a/src/frontend/tests/core/regression/ime-input-composition.spec.ts +++ b/src/frontend/tests/core/regression/ime-input-composition.spec.ts @@ -1,15 +1,10 @@ import type { Locator, Page } from "@playwright/test"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; async function addChromaNode(page: Page) { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("Chroma"); diff --git a/src/frontend/tests/core/regression/session-deletion-data-leakage.spec.ts b/src/frontend/tests/core/regression/session-deletion-data-leakage.spec.ts index 8320f79d06..714323c5ac 100644 --- a/src/frontend/tests/core/regression/session-deletion-data-leakage.spec.ts +++ b/src/frontend/tests/core/regression/session-deletion-data-leakage.spec.ts @@ -2,7 +2,9 @@ import type { Page } from "@playwright/test"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("Session Deletion Data Leakage Fix", () => { // Helper to send a message in the playground async function sendMessage(page: Page, message: string) { @@ -78,21 +80,19 @@ test.describe("Session Deletion Data Leakage Fix", () => { "should prevent data leakage when default session is deleted and recreated", { tag: ["@release", "@regression"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - + skipIfMissing.openAiKey(); await awaitBootstrapTest(page); // Load a starter project await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); @@ -145,21 +145,19 @@ test.describe("Session Deletion Data Leakage Fix", () => { "should clear LLM context when session is deleted", { tag: ["@release", "@regression"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - + skipIfMissing.openAiKey(); await awaitBootstrapTest(page); // Load a starter project with memory await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open playground await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts b/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts index 4ee5fe6de6..fa4602e4c2 100644 --- a/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts +++ b/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts @@ -72,15 +72,15 @@ test( "toggle_bool_add_calculator_tool", ); await expect(calculatorToggle).toBeVisible({ timeout: 10000 }); - expect(await calculatorToggle.isChecked()).toBeTruthy(); + await expect(calculatorToggle).toBeChecked(); // S3 — user disables the toggle. await calculatorToggle.click(); - expect(await calculatorToggle.isChecked()).toBeFalsy(); + await expect(calculatorToggle).not.toBeChecked(); // Re-enable to confirm the control is bi-directional. await calculatorToggle.click(); - expect(await calculatorToggle.isChecked()).toBeTruthy(); + await expect(calculatorToggle).toBeChecked(); }, ); diff --git a/src/frontend/tests/core/unit/chatInputOutput.spec.ts b/src/frontend/tests/core/unit/chatInputOutput.spec.ts index c2e9843b80..173ae000d1 100644 --- a/src/frontend/tests/core/unit/chatInputOutput.spec.ts +++ b/src/frontend/tests/core/unit/chatInputOutput.spec.ts @@ -1,20 +1,15 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="sidebar-search-input"]', { state: "visible", }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 2000, }); @@ -27,7 +22,7 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => { }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatInput); await page.waitForSelector('[data-testid="input_outputChat Input"]', { timeout: 2000, }); @@ -49,7 +44,7 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => { .click(); await page.getByTestId("handle-chatoutput-noshownode-inputs-target").click(); - await page.getByText("Playground", { exact: true }).last().click(); + await page.getByText(TEXTS.playground, { exact: true }).last().click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, }); diff --git a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts index 203f3ad0dc..fffcfdcdd8 100644 --- a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts @@ -1,18 +1,13 @@ import { expect } from "@playwright/test"; import { test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "CodeAreaModalComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 3000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("canvas_controls_dropdown").click(); @@ -66,7 +61,7 @@ class CustomComponent(Component): await page.keyboard.press(`ControlOrMeta+A`); await page.locator("textarea").fill(codeInputCode); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); await page.getByTestId("div-generic-node").click(); diff --git a/src/frontend/tests/core/unit/dropdownComponent.spec.ts b/src/frontend/tests/core/unit/dropdownComponent.spec.ts index 8a7509c81d..6526aca4ca 100644 --- a/src/frontend/tests/core/unit/dropdownComponent.spec.ts +++ b/src/frontend/tests/core/unit/dropdownComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,17 +7,14 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; + +import { TEXTS } from "../../utils/constants/texts"; test( "dropDownComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Allow for legacy components await page.getByTestId("sidebar-options-trigger").click(); @@ -152,7 +148,6 @@ from langflow.io import BoolInput, DictInput, DropdownInput, StrInput from langflow.io import MessageInput from langflow.io import Output - class AmazonBedrockComponent(LCModelComponent): display_name: str = "Amazon Bedrock" description: str = "Generate text using Amazon Bedrock LLMs." @@ -248,7 +243,7 @@ class AmazonBedrockComponent(LCModelComponent): return output `; await page.locator("textarea").fill(emptyOptionsCode); - await page.getByRole("button", { name: "Check & Save" }).click(); + await page.getByRole("button", { name: TEXTS.checkAndSave }).click(); await page .getByText("No parameters are available for display.") .isVisible(); diff --git a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts index c1715751d7..cc6daf295e 100644 --- a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts +++ b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts @@ -11,6 +11,9 @@ import { enableInspectPanel, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; + +import { TEXTS } from "../../utils/constants/texts"; // Run tests in this file serially to avoid database conflicts with shared file state test.describe.configure({ mode: "serial" }); @@ -30,13 +33,7 @@ test( // Read the test file content const testFilePath = path.join(__dirname, "../../assets/test_file.txt"); const fileContent = fs.readFileSync(testFilePath); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await disableInspectPanel(page); @@ -88,7 +85,7 @@ test( ), ).toBeVisible(); - await page.getByText("My Files").first().hover(); + await page.getByText(TEXTS.labelMyFiles).first().hover(); await page.waitForTimeout(500); await expect(page.getByText(`${sourceFileName}.txt`).last()).toBeVisible({ @@ -273,7 +270,7 @@ test( } await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page .getByTestId("input_outputChat Output") @@ -291,7 +288,9 @@ test( .first() .click(); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); // Create a new session first await page.getByTestId("new-chat").click(); @@ -387,7 +386,7 @@ test( .click(); await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click(); // Use the chat header more menu to clear chat (stays in fullscreen) await page @@ -445,13 +444,7 @@ test( // Read the test file content const testFilePath = path.join(__dirname, "../../assets/test_file.txt"); const _fileContent = fs.readFileSync(testFilePath); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await disableInspectPanel(page); @@ -696,7 +689,7 @@ test( await awaitBootstrapTest(page, { skipModal: true }); // Navigate to My Files page - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); // Check if we're on the files page await page.waitForSelector('[data-testid="mainpage_title"]'); @@ -903,7 +896,7 @@ test( }); await page.getByTestId("output-inspection-file path-file").click(); const filePaths = await page.getByTestId("textarea").textContent(); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); const cleanPath = filePaths ?.replace(/"/g, "") @@ -998,7 +991,9 @@ test( await textInputs.first().fill(`${folderId}/${file1}.txt`); await textInputs.last().fill(`${folderId}/${file2}.txt`); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector("text=Run Flow", { timeout: 30000, @@ -1011,7 +1006,7 @@ test( await expect(page.getByText(fileContent2)).toBeVisible(); await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click({ force: true }); // Test Case 2: Single File (clear second input, use only first) @@ -1019,7 +1014,7 @@ test( await textInputs.first().fill(`${folderId}/${file1}.txt`); await page - .getByRole("button", { name: "Playground", exact: true }) + .getByRole("button", { name: TEXTS.playground, exact: true }) .click({ force: true }); await page.waitForSelector("text=Run Flow", { diff --git a/src/frontend/tests/core/unit/floatComponent.spec.ts b/src/frontend/tests/core/unit/floatComponent.spec.ts index d009c8e29e..7b2470cf4d 100644 --- a/src/frontend/tests/core/unit/floatComponent.spec.ts +++ b/src/frontend/tests/core/unit/floatComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,16 +7,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "FloatComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("nvidia"); diff --git a/src/frontend/tests/core/unit/inputComponent.spec.ts b/src/frontend/tests/core/unit/inputComponent.spec.ts index c37e6e9024..5e30fe6cf9 100644 --- a/src/frontend/tests/core/unit/inputComponent.spec.ts +++ b/src/frontend/tests/core/unit/inputComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,16 +7,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "InputComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("Chroma"); diff --git a/src/frontend/tests/core/unit/inputListComponent.spec.ts b/src/frontend/tests/core/unit/inputListComponent.spec.ts index bc3ec226e3..3565dee098 100644 --- a/src/frontend/tests/core/unit/inputListComponent.spec.ts +++ b/src/frontend/tests/core/unit/inputListComponent.spec.ts @@ -8,6 +8,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { TEXTS } from "../../utils/constants/texts"; test( "InputListComponent", { tag: ["@release", "@workspace"] }, @@ -16,7 +17,7 @@ test( await page.getByTestId("blank-flow").click(); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchUrl); await page.waitForSelector('[data-testid="data_sourceURL"]', { timeout: 3000, diff --git a/src/frontend/tests/core/unit/intComponent.spec.ts b/src/frontend/tests/core/unit/intComponent.spec.ts index 8fe90bfd69..86e2218a9a 100644 --- a/src/frontend/tests/core/unit/intComponent.spec.ts +++ b/src/frontend/tests/core/unit/intComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,15 +7,15 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; -test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); +import { TEXTS } from "../../utils/constants/texts"; +test("IntComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.providerOpenAiSearch); await page.waitForSelector('[data-testid="openaiOpenAI"]', { timeout: 3000, diff --git a/src/frontend/tests/core/unit/keyPairListComponent.spec.ts b/src/frontend/tests/core/unit/keyPairListComponent.spec.ts index b5aa967168..099cf88af5 100644 --- a/src/frontend/tests/core/unit/keyPairListComponent.spec.ts +++ b/src/frontend/tests/core/unit/keyPairListComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,16 +7,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "KeypairListComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await disableInspectPanel(page); @@ -43,7 +38,7 @@ test( await openAdvancedOptions(page); await page.getByTestId("showmodel_kwargs").click(); - expect(await page.getByTestId("showmodel_kwargs").isChecked()).toBeTruthy(); + await expect(page.getByTestId("showmodel_kwargs")).toBeChecked(); await closeAdvancedOptions(page); await adjustScreenView(page, { diff --git a/src/frontend/tests/core/unit/linkComponent.spec.ts b/src/frontend/tests/core/unit/linkComponent.spec.ts index 075be0a8ba..7fe0defdbc 100644 --- a/src/frontend/tests/core/unit/linkComponent.spec.ts +++ b/src/frontend/tests/core/unit/linkComponent.spec.ts @@ -2,6 +2,7 @@ import { type Page } from "@playwright/test"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; // TODO: This test might not be needed anymore test( @@ -74,27 +75,6 @@ test( }, ); -async function extractAndCleanCode(page: Page): Promise { - const outerHTML = await page - .locator('//*[@id="codeValue"]') - .evaluate((el) => el.outerHTML); - - const valueMatch = outerHTML.match(/value="([\s\S]*?)"/); - if (!valueMatch) { - throw new Error("Could not find value attribute in the HTML"); - } - - const codeContent = valueMatch[1] - .replace(/"/g, '"') - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/'/g, "'") - .replace(///g, "/"); - - return codeContent; -} - function updateComponentCode( code: string, updates: { diff --git a/src/frontend/tests/core/unit/nestedComponent.spec.ts b/src/frontend/tests/core/unit/nestedComponent.spec.ts index dea2b3dacb..a1bf14ba45 100644 --- a/src/frontend/tests/core/unit/nestedComponent.spec.ts +++ b/src/frontend/tests/core/unit/nestedComponent.spec.ts @@ -1,7 +1,6 @@ import { expect, test } from "../../fixtures"; import { addLegacyComponents } from "../../utils/add-legacy-components"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -9,16 +8,14 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; + +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to use nested component", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("alter metadata"); @@ -57,7 +54,7 @@ test( expect(await page.getByText("proptest1", { exact: true }).count()).toBe(1); expect(await page.getByText("proptest2", { exact: true }).count()).toBe(1); - await page.getByText("Save").last().click(); + await page.getByText(TEXTS.save).last().click(); await disableInspectPanel(page); @@ -87,7 +84,7 @@ test( expect(await page.getByText("keytest", { exact: true }).count()).toBe(0); expect(await page.getByText("proptest", { exact: true }).count()).toBe(0); - await page.getByText("Save", { exact: true }).last().click(); + await page.getByText(TEXTS.save, { exact: true }).last().click(); await closeAdvancedOptions(page); diff --git a/src/frontend/tests/core/unit/promptModalComponent.spec.ts b/src/frontend/tests/core/unit/promptModalComponent.spec.ts index e29dfd7f39..687867d3a8 100644 --- a/src/frontend/tests/core/unit/promptModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/promptModalComponent.spec.ts @@ -1,7 +1,6 @@ import type { Page } from "@playwright/test"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -9,7 +8,9 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; // Helper function to verify prompt variables async function verifyPromptVariables( page: Page, @@ -62,13 +63,9 @@ test( "PromptTemplateComponent - Variable Extraction", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', { @@ -150,14 +147,9 @@ test( "PromptTemplateComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', @@ -294,7 +286,7 @@ test( ).toBeFalsy(); await page.locator('//*[@id="showprompt"]').click(); - expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeFalsy(); + await expect(page.locator('//*[@id="showprompt"]')).not.toBeChecked(); await page.locator('//*[@id="showprompt1"]').click(); expect( @@ -322,7 +314,7 @@ test( ).toBeFalsy(); await page.locator('//*[@id="showprompt"]').click(); - expect(await page.locator('//*[@id="showprompt"]').isChecked()).toBeFalsy(); + await expect(page.locator('//*[@id="showprompt"]')).not.toBeChecked(); await page.locator('//*[@id="showprompt1"]').click(); expect( @@ -384,14 +376,9 @@ test( "PromptTemplateComponent - Double Brackets Variable Extraction", { tag: ["@release", "@workspace"], timeout: 60000 }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', @@ -477,14 +464,9 @@ test( "PromptTemplateComponent - Double Brackets Multiple Variables", { tag: ["@release", "@workspace"], timeout: 60000 }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', @@ -612,14 +594,9 @@ test( "PromptTemplateComponent - Synchronized State", { tag: ["@release", "@workspace"], timeout: 60000 }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', diff --git a/src/frontend/tests/core/unit/queryInputComponent.spec.ts b/src/frontend/tests/core/unit/queryInputComponent.spec.ts index eb5ef7242d..b1e19793ce 100644 --- a/src/frontend/tests/core/unit/queryInputComponent.spec.ts +++ b/src/frontend/tests/core/unit/queryInputComponent.spec.ts @@ -9,6 +9,9 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; + +import { TEXTS } from "../../utils/constants/texts"; // TODO: This component doesn't have slider needs updating test( "user should be able to use query input", @@ -23,7 +26,9 @@ test( }); await page.getByTestId("blank-flow").click(); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.providerOpenAiSearch); await page.waitForSelector('[data-testid="openaiOpenAI"]', { timeout: 3000, @@ -129,24 +134,3 @@ test( await enableInspectPanel(page); }, ); - -async function extractAndCleanCode(page: Page): Promise { - const outerHTML = await page - .locator('//*[@id="codeValue"]') - .evaluate((el) => el.outerHTML); - - const valueMatch = outerHTML.match(/value="([\s\S]*?)"/); - if (!valueMatch) { - throw new Error("Could not find value attribute in the HTML"); - } - - const codeContent = valueMatch[1] - .replace(/"/g, '"') - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/'/g, "'") - .replace(///g, "/"); - - return codeContent; -} diff --git a/src/frontend/tests/core/unit/sliderComponent.spec.ts b/src/frontend/tests/core/unit/sliderComponent.spec.ts index 37e960d44d..be23a17eb7 100644 --- a/src/frontend/tests/core/unit/sliderComponent.spec.ts +++ b/src/frontend/tests/core/unit/sliderComponent.spec.ts @@ -9,6 +9,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; // TODO: This component doesn't have slider needs updating test( "user should be able to use slider input", @@ -111,44 +112,6 @@ test( }, ); -async function extractAndCleanCode(page: Page): Promise { - // The hidden `#codeValue` mirror is rendered with ``, - // i.e. a single-line `` rather than a textarea. Browsers strip - // newlines from `.value` of single-line inputs by HTML spec, so reading - // `(el as HTMLInputElement).value` returns the entire source concatenated - // onto one line. The backend then rejects it as - // `invalid decimal literal (, line 1)`. - // - // Read the rendered HTML attribute instead — React serializes the prop as - // `value="..."` with newlines encoded (` `, ` `, or literal LF - // inside attribute text), and the regex below recovers them faithfully. - // This is the same strategy queryInputComponent.spec.ts uses. - const outerHTML = await page - .locator('//*[@id="codeValue"]') - .evaluate((el) => el.outerHTML); - - const valueMatch = outerHTML.match(/value="([\s\S]*?)"/); - if (!valueMatch) { - throw new Error("Could not find value attribute in the #codeValue HTML"); - } - - const codeContent = valueMatch[1] - .replace(/"/g, '"') - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/'/g, "'") - .replace(///g, "/") - .replace(/ /g, "\n") - .replace(/ /g, "\r") - .replace(/ /gi, "\n") - .replace(/ /gi, "\r"); - - // Normalize line endings so downstream string operations are OS-agnostic - // (Windows git checkouts of .py files can end up with CRLF). - return codeContent.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); -} - // Set the Ace editor's content using the exact same pattern that // queryInputComponent.spec.ts uses successfully on Windows CI: // `textarea.last().fill(newCode)` against Ace's hidden text-input textarea. diff --git a/src/frontend/tests/core/unit/tabComponent.spec.ts b/src/frontend/tests/core/unit/tabComponent.spec.ts index 0e826e2284..46de387c68 100644 --- a/src/frontend/tests/core/unit/tabComponent.spec.ts +++ b/src/frontend/tests/core/unit/tabComponent.spec.ts @@ -2,6 +2,7 @@ import { type Page } from "@playwright/test"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; test( "user should interact with tab component", @@ -96,27 +97,6 @@ test( }, ); -async function extractAndCleanCode(page: Page): Promise { - const outerHTML = await page - .locator('//*[@id="codeValue"]') - .evaluate((el) => el.outerHTML); - - const valueMatch = outerHTML.match(/value="([\s\S]*?)"/); - if (!valueMatch) { - throw new Error("Could not find value attribute in the HTML"); - } - - const codeContent = valueMatch[1] - .replace(/"/g, '"') - .replace(/&/g, "&") - .replace(/</g, "<") - .replace(/>/g, ">") - .replace(/'/g, "'") - .replace(///g, "/"); - - return codeContent; -} - function updateComponentCode( code: string, updates: { diff --git a/src/frontend/tests/core/unit/tableInputComponent.spec.ts b/src/frontend/tests/core/unit/tableInputComponent.spec.ts deleted file mode 100644 index 1174d74180..0000000000 --- a/src/frontend/tests/core/unit/tableInputComponent.spec.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; -import { zoomOut } from "../../utils/zoom-out"; - -test.skip( - "user must be able to interact with table input component", - { - tag: ["@release", "@workspace"], - }, - async ({ page }) => { - // SKIP: This test has UI event conflicts where double-click should expose "Input Editor" - // but single-click opens textarea modal that blocks the view. This works in main but - // not in this branch despite no UI code changes. Needs investigation of event handling. - const randomText = Math.random().toString(36).substring(7); - const secondRandomText = Math.random().toString(36).substring(7); - const thirdRandomText = Math.random().toString(36).substring(7); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); - - await page.waitForSelector( - '[data-testid="sidebar-custom-component-button"]', - { - timeout: 3000, - }, - ); - - await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { - timeout: 3000, - }); - - await page.getByTestId("sidebar-custom-component-button").click(); - - await zoomOut(page, 2); - - await page.getByTestId("div-generic-node").click(); - await page.getByTestId("code-button-modal").last().click(); - - const customCodeWithError = ` -# from langflow.field_typing import Data -from langflow.custom import Component -from langflow.io import TableInput, Output -from langflow.schema import Data - - -class CustomComponent(Component): - display_name = "Custom Component" - description = "Use as a template to create your own component." - documentation: str = "https://docs.langflow.org/components-custom-components" - icon = "custom_components" - name = "CustomComponent" - - inputs = [ - TableInput( - name="input_value", - display_name="Input Value", - value=[ - {"alpha": "X1", "bravo": "Y2", "charlie": "Z3", "delta": "W4", "echo": "V5"}, - {"alpha": "A6", "bravo": "B7", "charlie": "C8", "delta": "D9", "echo": "E0"}, - {"alpha": "F1", "bravo": "G2", "charlie": "H3", "delta": "I4", "echo": "J5"}, - {"alpha": "K6", "bravo": "L7", "charlie": "M8", "delta": "N9", "echo": "O0"}, - {"alpha": "P1", "bravo": "Q2", "charlie": "R3", "delta": "S4", "echo": "T5"} - ], - table_schema=[ - {"name": "alpha", "display_name": "Alpha"}, - {"name": "bravo", "display_name": "Bravo"}, - {"name": "charlie", "display_name": "Charlie"}, - {"name": "delta", "display_name": "Delta"}, - {"name": "echo", "display_name": "Echo"} - ] -) - ] - - outputs = [ - Output(display_name="Output", name="output", method="build_output"), - ] - - def build_output(self) -> Data: - data = Data(value=self.input_value) - self.status = data - return data - `; - - await page.locator("textarea").press(`ControlOrMeta+a`); - await page.locator("textarea").fill(customCodeWithError); - - await page.getByText("Check & Save").last().click(); - - await page.waitForSelector('text="Open table"', { - timeout: 3000, - }); - - await page.getByText("Open table").click(); - - await page.waitForSelector(".ag-cell-value", { - timeout: 3000, - }); - - const visibleTextsGroup1 = ["Alpha", "Bravo", "Charlie", "Delta", "Echo"]; - const visibleTextsGroup2 = ["X1", "Y2", "Z3", "W4", "V5"]; - const visibleTextsGroup3 = ["P1", "Q2", "R3", "S4", "T5"]; - const visibleTextsGroup4 = ["F1", "G2", "H3", "I4", "J5"]; - - const allVisibleTexts = [ - ...visibleTextsGroup1, - ...visibleTextsGroup2, - ...visibleTextsGroup3, - ...visibleTextsGroup4, - ]; - - for (const text of allVisibleTexts) { - await expect(page.getByText(text).last()).toBeVisible(); - } - - await page.locator(".ag-cell-value").first().dblclick({ - force: true, - }); - - await page.getByLabel("Input Editor").fill(randomText); - await page.keyboard.press("Enter"); - - await page.locator(".ag-cell-value").nth(12).dblclick({ - force: true, - }); - - await page.getByLabel("Input Editor").fill(secondRandomText); - await page.keyboard.press("Enter"); - - await page.locator(".ag-cell-value").nth(24).dblclick({ - force: true, - }); - - await page.getByLabel("Input Editor").fill(thirdRandomText); - await page.keyboard.press("Enter"); - - expect(page.getByText(randomText)).toBeVisible(); - expect(page.getByText(secondRandomText)).toBeVisible(); - expect(page.getByText(thirdRandomText)).toBeVisible(); - - await page.locator('input[type="checkbox"]').last().click(); - - await page.getByTestId("icon-Copy").last().click(); - - await expect(page.getByTestId("duplicate-row-button")).toBeDisabled({ - timeout: 1000, - }); - - let numberOfCopiedRows = await page.getByText(thirdRandomText).count(); - expect(numberOfCopiedRows).toBe(2); - - await page.locator('input[type="checkbox"]').last().click(); - await page.getByTestId("icon-Trash2").last().click(); - - await expect(page.getByTestId("delete-row-button")).toBeDisabled({ - timeout: 1000, - }); - - await page.locator('input[type="checkbox"]').last().click(); - await page.getByTestId("icon-Trash2").click(); - - numberOfCopiedRows = await page.getByText(thirdRandomText).count(); - expect(numberOfCopiedRows).toBe(0); - - await page.getByText("Save").last().click(); - - await page.waitForSelector("text=Open table", { - timeout: 3000, - }); - - await page.getByText("Open table").click(); - - await page.waitForSelector(".ag-cell-value", { - timeout: 3000, - }); - - const visibleTexts = ["Alpha", "Bravo", "Charlie", "Delta", "Echo"]; - const notVisibleTexts = ["X1", "thirdRandomText"]; - - for (const text of visibleTexts) { - const count = await page.getByText(text).count(); - expect(count).toBeGreaterThanOrEqual(1); - } - - for (const text of notVisibleTexts) { - const count = await page.getByText(text).count(); - expect(count).toBe(0); - } - }, -); diff --git a/src/frontend/tests/core/unit/textAreaModalComponent.spec.ts b/src/frontend/tests/core/unit/textAreaModalComponent.spec.ts index 29f4d38c80..0879b269f1 100644 --- a/src/frontend/tests/core/unit/textAreaModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/textAreaModalComponent.spec.ts @@ -1,20 +1,16 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "TextAreaModalComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page.waitForSelector( '[data-testid="models_and_agentsPrompt Template"]', diff --git a/src/frontend/tests/core/unit/toggleComponent.spec.ts b/src/frontend/tests/core/unit/toggleComponent.spec.ts index f9c8eade85..c38b860b87 100644 --- a/src/frontend/tests/core/unit/toggleComponent.spec.ts +++ b/src/frontend/tests/core/unit/toggleComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,16 +7,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "ToggleComponent", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Open the sidebar options dropdown await page.getByTestId("sidebar-options-trigger").click(); @@ -108,7 +103,7 @@ test( ).toBeTruthy(); await page.locator('//*[@id="showpath"]').click(); - expect(await page.locator('//*[@id="showpath"]').isChecked()).toBeFalsy(); + await expect(page.locator('//*[@id="showpath"]')).not.toBeChecked(); await page.locator('//*[@id="showrecursive"]').click(); expect( @@ -131,7 +126,7 @@ test( ).toBeFalsy(); await page.locator('//*[@id="showpath"]').click(); - expect(await page.locator('//*[@id="showpath"]').isChecked()).toBeTruthy(); + await expect(page.locator('//*[@id="showpath"]')).toBeChecked(); await page.locator('//*[@id="showrecursive"]').click(); expect( diff --git a/src/frontend/tests/core/unit/webhookComponent.spec.ts b/src/frontend/tests/core/unit/webhookComponent.spec.ts index bdcf5ed46f..daa4721b96 100644 --- a/src/frontend/tests/core/unit/webhookComponent.spec.ts +++ b/src/frontend/tests/core/unit/webhookComponent.spec.ts @@ -1,6 +1,5 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { closeAdvancedOptions, disableInspectPanel, @@ -8,6 +7,9 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; + +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to create an api key within a webhook component", { tag: ["@release", "@workspace"] }, @@ -15,13 +17,7 @@ test( const _randomApiKeyDescription = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("webhook"); @@ -66,7 +62,7 @@ test( expect(curl).toContain(flowId); - await page.getByText("Close", { exact: true }).last().click(); + await page.getByText(TEXTS.close, { exact: true }).last().click(); await closeAdvancedOptions(page); await enableInspectPanel(page); }, @@ -94,13 +90,7 @@ test( const _randomApiKeyDescription = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("webhook"); diff --git a/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts b/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts index 3b5d882ba8..9729205bae 100644 --- a/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts +++ b/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts @@ -3,6 +3,7 @@ import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-em import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to download a flow or a component", { tag: ["@release", "@workspace"] }, @@ -10,10 +11,12 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await page.getByTestId("more-options-modal").click(); await page.getByTestId("icon-SaveAll").first().click(); @@ -26,17 +29,19 @@ test( timeout: 100000, }); - const exitButton = await page.getByText("Exit", { exact: true }).count(); + const exitButton = await page + .getByText(TEXTS.exit, { exact: true }) + .count(); if (exitButton > 0) { - await page.getByText("Exit", { exact: true }).click(); + await page.getByText(TEXTS.exit, { exact: true }).click(); } await page.getByTestId("icon-ChevronLeft").last().click(); await page.getByTestId("home-dropdown-menu").nth(0).click(); await page.getByTestId("btn-download-json").last().click(); - await page.getByText("Export").first().isVisible(); - await page.getByTestId("modal-export-button").isVisible(); + await expect(page.getByText("Export").first()).toBeVisible(); + await expect(page.getByTestId("modal-export-button")).toBeVisible(); await page.getByTestId("modal-export-button").click(); await expect(page.getByText(/.*exported successfully/)).toBeVisible({ timeout: 10000, @@ -45,15 +50,15 @@ test( await page.getByText("Flows", { exact: true }).click(); await page.getByTestId("home-dropdown-menu").nth(0).click(); await page.getByTestId("btn-download-json").last().click(); - await page.getByText("Export").first().isVisible(); - await page.getByTestId("modal-export-button").isVisible(); + await expect(page.getByText("Export").first()).toBeVisible(); + await expect(page.getByTestId("modal-export-button")).toBeVisible(); await page.getByTestId("modal-export-button").click(); await expect(page.getByText(/.*exported successfully/).last()).toBeVisible({ timeout: 10000, }); - if (await page.getByText("Components").first().isVisible()) { - await page.getByText("Components", { exact: true }).click(); + if (await page.getByText(TEXTS.labelComponents).first().isVisible()) { + await page.getByText(TEXTS.labelComponents, { exact: true }).click(); await page.getByTestId("home-dropdown-menu").nth(0).click(); await page.getByTestId("btn-download-json").last().click(); await expect( @@ -90,10 +95,12 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await page.getByTestId("more-options-modal").click(); await page.getByTestId("icon-SaveAll").first().click(); @@ -106,10 +113,12 @@ test( timeout: 100000, }); - const exitButton = await page.getByText("Exit", { exact: true }).count(); + const exitButton = await page + .getByText(TEXTS.exit, { exact: true }) + .count(); if (exitButton > 0) { - await page.getByText("Exit", { exact: true }).click(); + await page.getByText(TEXTS.exit, { exact: true }).click(); } const replaceButton = await page.getByTestId("replace-button").isVisible(); diff --git a/src/frontend/tests/extended/features/auto-save-off.spec.ts b/src/frontend/tests/extended/features/auto-save-off.spec.ts index 76b1a38342..60c7f2a44a 100644 --- a/src/frontend/tests/extended/features/auto-save-off.spec.ts +++ b/src/frontend/tests/extended/features/auto-save-off.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user should be able to manually save a flow when the auto_save is off", @@ -21,14 +21,7 @@ test( }, }); }); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 5000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("NVIDIA"); diff --git a/src/frontend/tests/extended/features/bulk-actions.spec.ts b/src/frontend/tests/extended/features/bulk-actions.spec.ts index 3cd95933cf..1d72175f5d 100644 --- a/src/frontend/tests/extended/features/bulk-actions.spec.ts +++ b/src/frontend/tests/extended/features/bulk-actions.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to select flows with different methods and perform bulk actions", { tag: ["@release", "@workspace", "@mainpage"] }, @@ -10,7 +11,9 @@ test( // Add some flows to test with await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page); // Go back to main page @@ -19,7 +22,7 @@ test( }); await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByText("Projects").first().isVisible(); + await expect(page.getByText("Projects").first()).toBeVisible(); await page.getByTestId("new-project-btn").click(); await page.getByTestId("side_nav_options_all-templates").click(); await page.getByRole("heading", { name: "Document Q&A" }).click(); @@ -28,16 +31,18 @@ test( }); await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByText("Projects").first().isVisible(); + await expect(page.getByText("Projects").first()).toBeVisible(); await page.getByTestId("new-project-btn").click(); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, }); await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByText("Projects").first().isVisible(); + await expect(page.getByText("Projects").first()).toBeVisible(); await page.waitForSelector('[data-testid="home-dropdown-menu"]', { timeout: 100000, }); @@ -108,7 +113,7 @@ test( await page.getByText("This can't be undone.").isVisible({ timeout: 1000, }); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); // Verify deletion success message await expect(page.getByText("Flows deleted successfully")).toBeVisible({ diff --git a/src/frontend/tests/extended/features/copy-button-in-output.spec.ts b/src/frontend/tests/extended/features/copy-button-in-output.spec.ts index 1079568aba..d63bbb0fd6 100644 --- a/src/frontend/tests/extended/features/copy-button-in-output.spec.ts +++ b/src/frontend/tests/extended/features/copy-button-in-output.spec.ts @@ -1,15 +1,14 @@ import { expect, test } from "../../fixtures"; import { addCustomComponent } from "../../utils/add-custom-component"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { zoomOut } from "../../utils/zoom-out"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to copy JSON from output", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="disclosure-data sources"]', { timeout: 3000, state: "visible", @@ -38,7 +37,9 @@ test( state: "visible", }); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.getByTestId("output-inspection-api response-apirequest").click(); diff --git a/src/frontend/tests/extended/features/curlApiGeneration.spec.ts b/src/frontend/tests/extended/features/curlApiGeneration.spec.ts deleted file mode 100644 index 6aca967603..0000000000 --- a/src/frontend/tests/extended/features/curlApiGeneration.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test( - "curl_api_generation", - { tag: ["@release", "@api", "@workspace"] }, - async ({ page, context }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); - await page.getByTestId("publish-button").click(); - await page.getByTestId("api-access-item").click(); - await page.getByTestId("api_tab_curl").click(); - await page.getByTestId("icon-Copy").last().click(); - const handle = await page.evaluateHandle(() => - navigator.clipboard.readText(), - ); - const clipboardContent = await handle.jsonValue(); - expect(clipboardContent.length).toBeGreaterThan(0); - }, -); diff --git a/src/frontend/tests/extended/features/deleteComponents.spec.ts b/src/frontend/tests/extended/features/deleteComponents.spec.ts index 9e13b0030e..8bcdb57242 100644 --- a/src/frontend/tests/extended/features/deleteComponents.spec.ts +++ b/src/frontend/tests/extended/features/deleteComponents.spec.ts @@ -1,18 +1,14 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "should delete a component (requires store API key)", { tag: ["@release", "@api"] }, async ({ page }) => { - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -21,11 +17,11 @@ test( timeout: 200000, }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -35,9 +31,11 @@ test( timeout: 100000, }); await page.getByTestId("icon-ChevronLeft").first().click(); - if (await page.getByText("Components").first().isVisible()) { - await page.getByText("Components").first().click(); - await page.getByText("Basic RAG").first().isVisible(); + if (await page.getByText(TEXTS.labelComponents).first().isVisible()) { + await page.getByText(TEXTS.labelComponents).first().click(); + await expect( + page.getByText(TEXTS.templateBasicRag).first(), + ).toBeVisible(); await page.waitForSelector('[data-testid="home-dropdown-menu"]', { timeout: 100000, }); @@ -46,9 +44,9 @@ test( await page .getByText("Are you sure you want to delete the selected component?") .isVisible(); - await page.getByText("Delete").nth(1).click(); + await page.getByText(TEXTS.delete).nth(1).click(); await page.waitForTimeout(1000); - await page.getByText("Successfully").first().isVisible(); + await expect(page.getByText("Successfully").first()).toBeVisible(); } }, ); diff --git a/src/frontend/tests/extended/features/deleteFlows.spec.ts b/src/frontend/tests/extended/features/deleteFlows.spec.ts index c6d7884611..17841506a7 100644 --- a/src/frontend/tests/extended/features/deleteFlows.spec.ts +++ b/src/frontend/tests/extended/features/deleteFlows.spec.ts @@ -1,19 +1,14 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "should delete a flow (requires store API key)", { tag: ["@release", "@api"] }, async ({ page }) => { - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await page.goto("/"); await page.waitForTimeout(1000); @@ -25,14 +20,13 @@ test( }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.waitForSelector('[data-testid="button-store"]', { timeout: 30000, }); @@ -57,19 +51,18 @@ test( await page.waitForSelector("text=Website Content QA", { timeout: 30000 }); - await page.getByText("Website Content QA").first().isVisible(); - + await expect(page.getByText("Website Content QA").first()).toBeVisible(); await page.getByTestId("home-dropdown-menu").first().click(); await page.waitForTimeout(500); - await page.getByText("Delete").last().click(); + await page.getByText(TEXTS.delete).last().click(); await page.waitForTimeout(500); await page .getByText("Are you sure you want to delete the selected component?") .isVisible(); - await page.getByText("Delete").nth(1).click(); + await page.getByText(TEXTS.delete).nth(1).click(); await page.waitForTimeout(1000); - await page.getByText("Successfully").first().isVisible(); + await expect(page.getByText("Successfully").first()).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/features/dragAndDrop.spec.ts b/src/frontend/tests/extended/features/dragAndDrop.spec.ts index 3120587698..f1d1e382ed 100644 --- a/src/frontend/tests/extended/features/dragAndDrop.spec.ts +++ b/src/frontend/tests/extended/features/dragAndDrop.spec.ts @@ -3,6 +3,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { simulateDragAndDrop } from "../../utils/simulate-drag-and-drop"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to drag and drop an old collection without crashing the application", { tag: ["@release", "@mainpage"] }, @@ -11,7 +12,9 @@ test( //add a new flow just to have the workspace available await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -65,7 +68,9 @@ test( //add a new flow just to have the workspace available await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, diff --git a/src/frontend/tests/extended/features/edit-flow-name.spec.ts b/src/frontend/tests/extended/features/edit-flow-name.spec.ts index c57aa17752..3f8ec7c080 100644 --- a/src/frontend/tests/extended/features/edit-flow-name.spec.ts +++ b/src/frontend/tests/extended/features/edit-flow-name.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to edit flow name by clicking on the header or on the main page", { tag: ["@release", "@workspace", "@components"] }, @@ -13,7 +14,9 @@ test( await awaitBootstrapTest(page); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await renameFlow(page, { flowName: randomName }); diff --git a/src/frontend/tests/extended/features/edit-name-description-node.spec.ts b/src/frontend/tests/extended/features/edit-name-description-node.spec.ts index 3e5909057a..ae7ab7d343 100644 --- a/src/frontend/tests/extended/features/edit-name-description-node.spec.ts +++ b/src/frontend/tests/extended/features/edit-name-description-node.spec.ts @@ -1,10 +1,10 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { disableInspectPanel, enableInspectPanel, } from "../../utils/open-advanced-options"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user should be able to edit name and description of a node", @@ -22,13 +22,7 @@ test( const randomName_4 = Math.random().toString(36).substring(2, 15); const randomDescription_4 = Math.random().toString(36).substring(2, 15); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector( '[data-testid="sidebar-custom-component-button"]', @@ -139,13 +133,7 @@ test( const randomName_4 = Math.random().toString(36).substring(2, 15); const randomDescription_4 = Math.random().toString(36).substring(2, 15); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector( '[data-testid="sidebar-custom-component-button"]', diff --git a/src/frontend/tests/extended/features/edit-tools.spec.ts b/src/frontend/tests/extended/features/edit-tools.spec.ts index f0bd618410..dab469ea6b 100644 --- a/src/frontend/tests/extended/features/edit-tools.spec.ts +++ b/src/frontend/tests/extended/features/edit-tools.spec.ts @@ -1,16 +1,15 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to edit tools", { tag: ["@release", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchUrl); await page.waitForSelector('[data-testid="data_sourceURL"]', { timeout: 3000, @@ -105,7 +104,7 @@ test( await page.waitForTimeout(500); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.waitForTimeout(500); @@ -188,7 +187,7 @@ test( timeout: 3000, }); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); expect( await page.locator('[data-testid="tool_fetch_content"]').isVisible(), diff --git a/src/frontend/tests/extended/features/files-page.spec.ts b/src/frontend/tests/extended/features/files-page.spec.ts index e540f83676..1a096ea352 100644 --- a/src/frontend/tests/extended/features/files-page.spec.ts +++ b/src/frontend/tests/extended/features/files-page.spec.ts @@ -5,6 +5,7 @@ import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-em import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { generateRandomFilename } from "../../utils/generate-filename"; +import { TEXTS } from "../../utils/constants/texts"; // Configure tests to run serially with a delay between each test test( "should navigate to files page and show empty state", @@ -25,7 +26,7 @@ test( }); // Click on the files button - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); // Check if we're on the files page await page.waitForSelector('[data-testid="mainpage_title"]'); @@ -69,7 +70,7 @@ test( timeout: 30000, }); - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByTestId("upload-file-btn").click(); @@ -112,7 +113,7 @@ test( timeout: 30000, }); - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); // Create DataTransfer object and file const dataTransfer = await page.evaluateHandle((fileName) => { @@ -180,7 +181,7 @@ test( timeout: 30000, }); - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByTestId("upload-file-btn").click(); @@ -252,7 +253,7 @@ test( timeout: 30000, }); - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByTestId("upload-file-btn").click(); @@ -351,7 +352,7 @@ test( timeout: 30000, }); - await page.getByText("My Files").first().click(); + await page.getByText(TEXTS.labelMyFiles).first().click(); const fileChooserPromise = page.waitForEvent("filechooser"); await page.getByTestId("upload-file-btn").click(); @@ -404,9 +405,9 @@ test( await jsonCheckbox.click(); await pyCheckbox.click(); - expect(await txtCheckbox.isChecked()).toBe(true); - expect(await jsonCheckbox.isChecked()).toBe(true); - expect(await pyCheckbox.isChecked()).toBe(true); + await expect(txtCheckbox).toBeChecked(); + await expect(jsonCheckbox).toBeChecked(); + await expect(pyCheckbox).toBeChecked(); // Check if the bulk actions toolbar appears const deleteButton = await page.getByTestId("bulk-delete-btn"); @@ -428,7 +429,7 @@ test( // Confirm the delete in the modal const confirmDeleteButton = await page.getByRole("button", { - name: "Delete", + name: TEXTS.delete, }); await confirmDeleteButton.click(); diff --git a/src/frontend/tests/extended/features/flowPage.spec.ts b/src/frontend/tests/extended/features/flowPage.spec.ts deleted file mode 100644 index f8afe910d5..0000000000 --- a/src/frontend/tests/extended/features/flowPage.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test.describe("Flow Page tests", () => { - test("save", { tag: ["@release"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); - - await page.waitForSelector( - '[data-testid="sidebar-custom-component-button"]', - { - timeout: 3000, - }, - ); - - await page.getByTestId("sidebar-custom-component-button").click(); - - await adjustScreenView(page, { numberOfZoomOut: 3 }); - }); -}); diff --git a/src/frontend/tests/extended/features/flowSettings.spec.ts b/src/frontend/tests/extended/features/flowSettings.spec.ts index d464ff90e2..b060085015 100644 --- a/src/frontend/tests/extended/features/flowSettings.spec.ts +++ b/src/frontend/tests/extended/features/flowSettings.spec.ts @@ -1,17 +1,12 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "flowSettings", { tag: ["@release", "@api", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("flow_name").isVisible({ timeout: 3000 }); await page.getByTestId("flow_name").click(); @@ -25,8 +20,7 @@ test( "Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test Flow Name Test", ); - await page.getByText("Character limit reached").isVisible(); - + await expect(page.getByText("Character limit reached")).toBeVisible(); await page.getByTestId("input-flow-name").click(); const randomName = Math.random().toString(36).substring(2); await page.getByTestId("input-flow-name").fill(randomName); diff --git a/src/frontend/tests/extended/features/integration-side-bar.spec.ts b/src/frontend/tests/extended/features/integration-side-bar.spec.ts index 6fe25e0931..8c45bf7bd0 100644 --- a/src/frontend/tests/extended/features/integration-side-bar.spec.ts +++ b/src/frontend/tests/extended/features/integration-side-bar.spec.ts @@ -1,13 +1,11 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user should be able to see integrations in the sidebar when bundles is selected", { tag: ["@release", "@api", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="shad-sidebar"]', { timeout: 30000, }); diff --git a/src/frontend/tests/extended/features/langflowShortcuts.spec.ts b/src/frontend/tests/extended/features/langflowShortcuts.spec.ts index f75293651d..c234a472b9 100644 --- a/src/frontend/tests/extended/features/langflowShortcuts.spec.ts +++ b/src/frontend/tests/extended/features/langflowShortcuts.spec.ts @@ -1,17 +1,12 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "LangflowShortcuts", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("ollama"); diff --git a/src/frontend/tests/extended/features/limit-file-size-upload.spec.ts b/src/frontend/tests/extended/features/limit-file-size-upload.spec.ts index 00d75312c5..b5aad37e1b 100644 --- a/src/frontend/tests/extended/features/limit-file-size-upload.spec.ts +++ b/src/frontend/tests/extended/features/limit-file-size-upload.spec.ts @@ -1,7 +1,8 @@ -import * as dotenv from "dotenv"; import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { closeAdvancedOptions, @@ -10,6 +11,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should not be able to upload a file larger than the limit", { tag: ["@release", "@api", "@database"] }, @@ -28,29 +30,26 @@ test( }, }); }); - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); await page.waitForSelector("text=Chat Input", { timeout: 30000 }); await disableInspectPanel(page); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await openAdvancedOptions(page); await closeAdvancedOptions(page); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, diff --git a/src/frontend/tests/extended/features/lock-flow.spec.ts b/src/frontend/tests/extended/features/lock-flow.spec.ts index ed7e317111..df10e77f68 100644 --- a/src/frontend/tests/extended/features/lock-flow.spec.ts +++ b/src/frontend/tests/extended/features/lock-flow.spec.ts @@ -1,29 +1,25 @@ import type { Page } from "@playwright/test"; -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { lockFlow, unlockFlow } from "../../utils/lock-flow"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to lock a flow and it must be saved", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, @@ -91,7 +87,7 @@ test( await unselectNodes(page); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await adjustScreenView(page); diff --git a/src/frontend/tests/extended/features/loop-component.spec.ts b/src/frontend/tests/extended/features/loop-component.spec.ts index 9961c2ac5f..734b813a6e 100644 --- a/src/frontend/tests/extended/features/loop-component.spec.ts +++ b/src/frontend/tests/extended/features/loop-component.spec.ts @@ -1,16 +1,16 @@ import { expect, test } from "../../fixtures"; import { addLegacyComponents } from "../../utils/add-legacy-components"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { uploadFile } from "../../utils/upload-file"; import { zoomOut } from "../../utils/zoom-out"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "should process loop with update data correctly", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await addLegacyComponents(page); @@ -23,7 +23,7 @@ test( // Add URL component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchUrl); await page.waitForSelector('[data-testid="data_sourceURL"]', { timeout: 1000, }); @@ -98,7 +98,7 @@ test( // Add Chat Output component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.locator(".react-flow__renderer").click(); @@ -194,7 +194,9 @@ test( // Build and run, expect the wrong loop message await page.getByTestId("button_run_read file").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); // Delete the second parse data used to test @@ -202,7 +204,7 @@ test( await page.getByTestId("more-options-modal").click(); - await page.getByText("Delete").first().click(); + await page.getByText(TEXTS.delete).first().click(); // Update Data -> Loop Item (left side) @@ -228,7 +230,9 @@ test( await expect(chatOutputInspectionButton).toBeEnabled({ timeout: 60000 }); await chatOutputInspectionButton.click(); - const output = await page.getByPlaceholder("Empty").textContent(); + const output = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .textContent(); expect(output).toContain("modified_value"); // Count occurrences of modified_value in output diff --git a/src/frontend/tests/extended/features/mcp-server-starter-projects.spec.ts b/src/frontend/tests/extended/features/mcp-server-starter-projects.spec.ts index 2f415aa20e..5041ec2fa5 100644 --- a/src/frontend/tests/extended/features/mcp-server-starter-projects.spec.ts +++ b/src/frontend/tests/extended/features/mcp-server-starter-projects.spec.ts @@ -4,6 +4,7 @@ import { cleanOldFolders } from "../../utils/clean-old-folders"; import { convertTestName } from "../../utils/convert-test-name"; import { navigateSettingsPages } from "../../utils/go-to-settings"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to see starter projects for mcp servers", { tag: ["@release", "@workspace", "@components"] }, @@ -47,11 +48,14 @@ test( //rename a folder const getFirstFolderName = convertTestName( - (await page.getByText("New Project").first().textContent()) as string, + (await page + .getByText(TEXTS.labelNewProject) + .first() + .textContent()) as string, ); await page - .getByText("New Project") + .getByText(TEXTS.labelNewProject) .first() .hover() .then(async () => { @@ -86,8 +90,8 @@ test( .getByTestId("more-options-button_renamed_project") .last() .click(); - await page.getByText("Delete", { exact: true }).last().click(); - await page.getByText("Delete", { exact: true }).last().click(); + await page.getByText(TEXTS.delete, { exact: true }).last().click(); + await page.getByText(TEXTS.delete, { exact: true }).last().click(); await page.waitForTimeout(1000); }); @@ -109,7 +113,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, 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 51ca75eaf1..b4f068cca2 100644 --- a/src/frontend/tests/extended/features/mcp-server-tab.spec.ts +++ b/src/frontend/tests/extended/features/mcp-server-tab.spec.ts @@ -3,6 +3,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { openAddMcpServerModal } from "../../utils/open-add-mcp-server-modal"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to manage MCP server tools and configuration", { tag: ["@release", "@workspace", "@components"] }, @@ -70,7 +71,7 @@ test( await expect(checkbox).toBeChecked(); // Close the modal - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); // Wait for modal to close await expect(page.getByText("MCP Server Tools")).not.toBeVisible(); @@ -116,7 +117,7 @@ test( await page.getByTestId("input_update_name").fill("mcp test name"); // Close the modal - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); // Wait for modal to close await expect(page.getByText("MCP Server Tools")).not.toBeVisible(); diff --git a/src/frontend/tests/extended/features/mcp-server.spec.ts b/src/frontend/tests/extended/features/mcp-server.spec.ts index 97529b8220..2d4159dbec 100644 --- a/src/frontend/tests/extended/features/mcp-server.spec.ts +++ b/src/frontend/tests/extended/features/mcp-server.spec.ts @@ -3,19 +3,15 @@ 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"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; 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"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForSelector( '[data-testid="add-component-button-lf-starter_project"]', @@ -169,7 +165,7 @@ test( await page.waitForTimeout(500); await page - .getByText("Delete", { exact: true }) + .getByText(TEXTS.delete, { exact: true }) .first() .click({ timeout: 3000 }); @@ -198,12 +194,7 @@ test( "user must be able to add and delete MCP server from sidebar", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForTimeout(500); @@ -335,12 +326,7 @@ test( "STDIO MCP server fields should persist after saving and editing", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForSelector( '[data-testid="add-component-button-lf-starter_project"]', @@ -464,7 +450,7 @@ test( .click({ timeout: 3000 }); await page - .getByText("Delete", { exact: true }) + .getByText(TEXTS.delete, { exact: true }) .first() .click({ timeout: 3000 }); @@ -485,12 +471,7 @@ test( "HTTP/SSE MCP server fields should persist after saving and editing", { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForSelector( '[data-testid="add-component-button-lf-starter_project"]', @@ -653,7 +634,7 @@ test( .click({ timeout: 10000 }); await page - .getByText("Delete", { exact: true }) + .getByText(TEXTS.delete, { exact: true }) .first() .click({ timeout: 10000 }); @@ -675,13 +656,7 @@ test( { tag: ["@release", "@workspace", "@components"] }, async ({ page }) => { await page.waitForTimeout(5000); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForSelector( '[data-testid="add-component-button-lf-starter_project"]', @@ -898,7 +873,7 @@ test( .click({ timeout: 10000 }); await page - .getByText("Delete", { exact: true }) + .getByText(TEXTS.delete, { exact: true }) .first() .click({ timeout: 10000 }); @@ -996,12 +971,7 @@ test( async ({ page }) => { // Start the MCP server with proper health checking const server = "https://mcp.deepwiki.com/mcp"; - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-nav-mcp").click(); await page.waitForSelector( '[data-testid="add-component-button-lf-starter_project"]', diff --git a/src/frontend/tests/extended/features/minimize.spec.ts b/src/frontend/tests/extended/features/minimize.spec.ts index 1a29744172..7a689875f9 100644 --- a/src/frontend/tests/extended/features/minimize.spec.ts +++ b/src/frontend/tests/extended/features/minimize.spec.ts @@ -1,21 +1,17 @@ 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 { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to minimize and expand a component", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); await page.waitForSelector('[data-testid="input_outputText Input"]', { timeout: 30000, }); diff --git a/src/frontend/tests/extended/features/notifications.spec.ts b/src/frontend/tests/extended/features/notifications.spec.ts index af291bc854..c03af1fe01 100644 --- a/src/frontend/tests/extended/features/notifications.spec.ts +++ b/src/frontend/tests/extended/features/notifications.spec.ts @@ -1,12 +1,12 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "User should be able to interact notifications tab", { tag: ["@release"] }, async ({ page }) => { - await awaitBootstrapTest(page); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="disclosure-input & output"]', { timeout: 3000, state: "visible", @@ -30,7 +30,9 @@ test( state: "visible", }); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.getByTestId("notification_button").click(); // Add explicit waits before checking visibility diff --git a/src/frontend/tests/extended/features/outdated-actions.spec.ts b/src/frontend/tests/extended/features/outdated-actions.spec.ts index 7e57c607e6..c11a1c8abd 100644 --- a/src/frontend/tests/extended/features/outdated-actions.spec.ts +++ b/src/frontend/tests/extended/features/outdated-actions.spec.ts @@ -71,7 +71,7 @@ test("user must be able to update outdated components by update all button", asy ).toBe(true); } - expect(await page.getByTestId("backup-flow-checkbox").isChecked()).toBe(true); + await expect(page.getByTestId("backup-flow-checkbox")).toBeChecked(); await page.getByTestId("backup-flow-checkbox").click(); await page.getByRole("button", { name: "Update Components" }).click(); @@ -140,7 +140,7 @@ test("user must be able to update outdated components by each outdated component timeout: 30000, }); - expect(await page.getByTestId("backup-flow-checkbox").isChecked()).toBe(true); + await expect(page.getByTestId("backup-flow-checkbox")).toBeChecked(); await page.getByRole("button", { name: "Update Component" }).click(); diff --git a/src/frontend/tests/extended/features/output-modal-copy-button.spec.ts b/src/frontend/tests/extended/features/output-modal-copy-button.spec.ts index dc79a65d0a..c0d3821c3e 100644 --- a/src/frontend/tests/extended/features/output-modal-copy-button.spec.ts +++ b/src/frontend/tests/extended/features/output-modal-copy-button.spec.ts @@ -1,14 +1,13 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test.describe("Output Modal Copy Button", () => { test( "user should be able to copy text output from component output modal", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 3000, @@ -17,7 +16,9 @@ test.describe("Output Modal Copy Button", () => { // Add a Text Input component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.searchTextInput); await page.waitForSelector('[data-testid="input_outputText Input"]', { timeout: 3000, @@ -41,7 +42,9 @@ test.describe("Output Modal Copy Button", () => { // Run the component await page.getByTestId("button_run_text input").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); // Open the output modal await page.locator('[data-testid^="output-inspection-"]').first().click(); @@ -77,9 +80,7 @@ test.describe("Output Modal Copy Button", () => { "copy button should work with JSON output from API Request component", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="disclosure-data sources"]', { timeout: 3000, @@ -109,7 +110,9 @@ test.describe("Output Modal Copy Button", () => { state: "visible", }); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page .getByTestId("output-inspection-api response-apirequest") diff --git a/src/frontend/tests/extended/features/pythonApiGeneration.spec.ts b/src/frontend/tests/extended/features/pythonApiGeneration.spec.ts index 2f241bacb2..c69dc6dea6 100644 --- a/src/frontend/tests/extended/features/pythonApiGeneration.spec.ts +++ b/src/frontend/tests/extended/features/pythonApiGeneration.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "python_api_generation", { tag: ["@release", "@api", "@workspace"] }, @@ -8,7 +9,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.getByTestId("publish-button").click(); await page.getByTestId("api-access-item").click(); await page.getByTestId("api_tab_python").click(); diff --git a/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts b/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts index 3ae1afdfed..efa7613673 100644 --- a/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts +++ b/src/frontend/tests/extended/features/refresh-dropdown-list.spec.ts @@ -1,8 +1,7 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; test( "refresh dropdown list", @@ -12,11 +11,7 @@ test( !process?.env?.ANTHROPIC_API_KEY, "ANTHROPIC_API_KEY required to run this test", ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + loadDotenvIfLocal(__dirname); await page.goto("/"); await awaitBootstrapTest(page); diff --git a/src/frontend/tests/extended/features/sticky-notes-dimensions.spec.ts b/src/frontend/tests/extended/features/sticky-notes-dimensions.spec.ts index 9f6ef06230..ca4da1f8c4 100644 --- a/src/frontend/tests/extended/features/sticky-notes-dimensions.spec.ts +++ b/src/frontend/tests/extended/features/sticky-notes-dimensions.spec.ts @@ -1,18 +1,13 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "sticky notes should have consistent 280x140px dimensions", { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Take reference element for size comparison const targetElement = page.locator('//*[@id="react-flow-id"]'); @@ -69,12 +64,7 @@ test( { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Add sticky note await page.getByTestId("canvas-add-note-button").click(); @@ -131,12 +121,7 @@ test( { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Add sticky note await page.getByTestId("canvas-add-note-button").click(); @@ -192,12 +177,7 @@ test( { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Add sticky note await page.getByTestId("canvas-add-note-button").click(); @@ -263,12 +243,7 @@ test( { tag: ["@release", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); // Add sticky note await page.getByTestId("canvas-add-note-button").click(); diff --git a/src/frontend/tests/extended/features/sticky-notes.spec.ts b/src/frontend/tests/extended/features/sticky-notes.spec.ts index 1d81f26eea..1d94b3cda5 100644 --- a/src/frontend/tests/extended/features/sticky-notes.spec.ts +++ b/src/frontend/tests/extended/features/sticky-notes.spec.ts @@ -1,9 +1,10 @@ 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 { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to interact with sticky notes", { tag: ["@release", "@workspace"] }, @@ -35,13 +36,7 @@ Despite its many benefits, AI also raises important ethical and societal questio The future of AI is both exciting and uncertain. As the technology continues to advance, it will undoubtedly bring about profound changes in society. The challenge will be to harness AI's potential for good while addressing the ethical and societal issues that arise. Whether it's through smarter healthcare, more efficient transportation, or enhanced creativity, AI has the potential to reshape the world in ways we are only beginning to imagine. The journey of AI is far from over, and its impact will be felt for generations to come. `; - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("canvas-add-note-button").click(); const targetElement = page.locator('//*[@id="react-flow-id"]'); @@ -128,7 +123,7 @@ The future of AI is both exciting and uncertain. As the technology continues to await page.getByTestId("note_node").last().click(); await page.getByTestId("more-options-modal").click(); - await page.getByText("Delete").first().click(); + await page.getByText(TEXTS.delete).first().click(); titleNumber = await page.getByText(randomTitle).count(); diff --git a/src/frontend/tests/extended/features/stop-button-playground.spec.ts b/src/frontend/tests/extended/features/stop-button-playground.spec.ts index 5a15cae7b0..adcc350bae 100644 --- a/src/frontend/tests/extended/features/stop-button-playground.spec.ts +++ b/src/frontend/tests/extended/features/stop-button-playground.spec.ts @@ -1,59 +1,13 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { addComponentFromSidebar } from "../../utils/flow/add-component-from-sidebar"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { replaceComponentCode } from "../../utils/flow/replace-component-code"; -import { zoomOut } from "../../utils/zoom-out"; - -test( - "User must be able to stop building from inside Playground", - { tag: ["@release", "@api"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); - - await page.waitForSelector( - '[data-testid="sidebar-custom-component-button"]', - { - timeout: 3000, - }, - ); - - await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { - timeout: 3000, - }); - - await page.getByTestId("sidebar-custom-component-button").click(); - await adjustScreenView(page); - - await page.getByTestId("sidebar-search-input").click(); - await page.waitForTimeout(500); - await page.getByTestId("sidebar-search-input").fill("chat output"); - await page.waitForTimeout(500); - - await page.waitForSelector('[data-testid="input_outputChat Output"]', { - timeout: 3000, - }); - - await page - .getByTestId("input_outputChat Output") - .dragTo(page.locator('//*[@id="react-flow-id"]'), { - targetPosition: { x: 400, y: 400 }, - }); - - await adjustScreenView(page); - - await page.getByTestId("div-generic-node").nth(1).click(); - - await page.getByTestId("more-options-modal").click(); - - await page.getByTestId("expand-button-modal").click(); - - await page.getByTestId("div-generic-node").nth(0).click(); - - await page.getByTestId("code-button-modal").nth(0).click(); - - const waitTimeoutCode = ` +import { TEXTS } from "../../utils/constants/texts"; +const SLEEP_60_CUSTOM_COMPONENT = ` # from langflow.field_typing import Data from langflow.custom import Component from langflow.io import MessageTextInput, Output @@ -82,14 +36,45 @@ class CustomComponent(Component): sleep(60) return data`; - await page.locator(".ace_content").click(); - await page.keyboard.press(`ControlOrMeta+A`); - await page.locator("textarea").fill(waitTimeoutCode); +test( + "User must be able to stop building from inside Playground", + { tag: ["@release", "@api"] }, + async ({ page }) => { + await openBlankFlow(page); - await page.getByText("Check & Save").last().click(); + await page.waitForSelector( + `[data-testid="${TID.sidebarCustomComponentButton}"]`, + { timeout: TIMEOUTS.short }, + ); + + await page.waitForSelector( + `[data-testid="${TID.canvasControlsDropdown}"]`, + { + timeout: TIMEOUTS.short, + }, + ); + + await page.getByTestId(TID.sidebarCustomComponentButton).click(); + await adjustScreenView(page); + + await addComponentFromSidebar(page, { + search: "chat output", + testId: "input_outputChat Output", + position: { x: 400, y: 400 }, + }); + + await adjustScreenView(page); + + await page.getByTestId(TID.divGenericNode).nth(1).click(); + await page.getByTestId("more-options-modal").click(); + await page.getByTestId("expand-button-modal").click(); + + await page.getByTestId(TID.divGenericNode).nth(0).click(); + + await replaceComponentCode(page, SLEEP_60_CUSTOM_COMPONENT); await adjustScreenView(page, { numberOfZoomOut: 2 }); - //connection 1 + // Connect Custom Component output → Chat Output input const elementCustomComponentOutput = await page .getByTestId("handle-customcomponent-shownode-output-right") .first(); @@ -102,30 +87,32 @@ class CustomComponent(Component): await elementChatOutput.hover(); await page.mouse.up(); - await page.waitForSelector('[data-testid="button_run_chat output"]', { - timeout: 3000, + await page.waitForSelector(`[data-testid="${TID.buttonRunChatOutput}"]`, { + timeout: TIMEOUTS.short, }); - await page.getByTestId("button_run_chat output").click(); + await page.getByTestId(TID.buttonRunChatOutput).click(); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); - await page.waitForSelector('[data-testid="button-stop"]', { - timeout: 30000, + await page.waitForSelector(`[data-testid="${TID.buttonStop}"]`, { + timeout: TIMEOUTS.standard, }); - const elements = await page.$$('[data-testid="button-stop"]'); - + const elements = await page.$$(`[data-testid="${TID.buttonStop}"]`); if (elements.length > 0) { const lastElement = elements[elements.length - 1]; await lastElement.waitForElementState("visible"); } - expect(await page.getByTestId("button-stop").last()).toBeVisible(); + await expect(page.getByTestId(TID.buttonStop).last()).toBeVisible(); + await page.getByTestId(TID.buttonStop).last().click(); - await page.getByTestId("button-stop").last().click(); - - await page.waitForSelector("text=build stopped", { timeout: 30000 }); - expect(await page.getByText("build stopped").isVisible()).toBeTruthy(); + await page.waitForSelector("text=build stopped", { + timeout: TIMEOUTS.standard, + }); + await expect(page.getByText("build stopped")).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/features/store-shard-0.spec.ts b/src/frontend/tests/extended/features/store-shard-0.spec.ts index d9b2586505..f2a68f1bb4 100644 --- a/src/frontend/tests/extended/features/store-shard-0.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-0.spec.ts @@ -1,10 +1,10 @@ -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test.skip("should exists Store", { tag: ["@release"] }, async ({ page }) => { await awaitBootstrapTest(page, { skipModal: true }); - await page.getByTestId("button-store").isVisible(); + await expect(page.getByTestId("button-store")).toBeVisible(); await page.getByTestId("button-store").isEnabled(); }); @@ -16,6 +16,6 @@ test.skip( await page.getByTestId("button-store").click(); - await page.getByText("API Key Error").isVisible(); + await expect(page.getByText("API Key Error")).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/features/store-shard-1.spec.ts b/src/frontend/tests/extended/features/store-shard-1.spec.ts index 8ceaae4f4e..75aab63569 100644 --- a/src/frontend/tests/extended/features/store-shard-1.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-1.spec.ts @@ -1,22 +1,18 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test.skip( "should like and add components and flows (requires store API key)", { tag: ["@release"] }, async ({ page }) => { - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page, { skipModal: true }); - await page.getByText("Close", { exact: true }).click(); + await page.getByText(TEXTS.close, { exact: true }).click(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); @@ -24,17 +20,17 @@ test.skip( timeout: 200000, }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); await page.waitForSelector('[data-testid="likes-Website Content QA"]', { timeout: 100000, }); - await page.getByTestId("likes-Website Content QA").isVisible(); + await expect(page.getByTestId("likes-Website Content QA")).toBeVisible(); await page.waitForTimeout(1000); const likedValue = await page .getByTestId("likes-Website Content QA") @@ -47,9 +43,7 @@ test.skip( const likedValueAfter = await page .getByTestId("likes-Website Content QA") .innerText(); - if (Number(likedValue) === Number(likedValueAfter)) { - expect(false).toBe(true); - } + expect(Number(likedValueAfter)).not.toBe(Number(likedValue)); await page.waitForSelector('[data-testid="downloads-Website Content QA"]', { timeout: 100000, }); @@ -59,27 +53,27 @@ test.skip( await page.waitForTimeout(1000); await page.getByTestId("install-Website Content QA").click(); await page.waitForTimeout(1000); - await page.getByText("Flow Installed Successfully").isVisible(); + await expect(page.getByText("Flow Installed Successfully")).toBeVisible(); await page.waitForTimeout(1000); const downloadValueAfter = await page .getByTestId("downloads-Website Content QA") .innerText(); - if (Number(downloadValue) === Number(downloadValueAfter)) { - expect(false).toBe(true); - } + expect(Number(downloadValueAfter)).not.toBe(Number(downloadValue)); await page.getByTestId("install-Basic RAG").click(); await page.waitForTimeout(1000); - await page.getByText("Component Installed Successfully").isVisible(); + await expect( + page.getByText("Component Installed Successfully"), + ).toBeVisible(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, }); await page.getByTestId("icon-ChevronLeft").first().click(); await page.waitForSelector("text=Website Content QA", { timeout: 30000 }); - await page.getByText("Website Content QA").first().isVisible(); - await page.getByText("Components").first().click(); + await expect(page.getByText("Website Content QA").first()).toBeVisible(); + await page.getByText(TEXTS.labelComponents).first().click(); await page.waitForTimeout(1000); await page.waitForSelector("text=Basic RAG", { timeout: 30000 }); - await page.getByText("Basic RAG").first().isVisible(); + await expect(page.getByText(TEXTS.templateBasicRag).first()).toBeVisible(); }, ); @@ -87,15 +81,8 @@ test.skip( "should find a searched Component on Store (requires store API key)", { tag: ["@release"] }, async ({ page }) => { - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page, { skipModal: true }); await page.waitForTimeout(1000); @@ -108,14 +95,13 @@ test.skip( }); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); await page.waitForTimeout(1000); - await page.getByText("Success! Your API Key has been saved.").isVisible(); - + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -126,14 +112,12 @@ test.skip( await page.getByTestId("search-store-input").fill("File Loader"); await page.getByTestId("search-store-button").click(); - await page.getByText("File Loader").isVisible(); - + await expect(page.getByText("File Loader")).toBeVisible(); await page.getByTestId("search-store-input").fill("Basic RAG"); await page.getByTestId("search-store-button").click(); - await page.getByText("Basic RAG").isVisible(); - + await expect(page.getByText(TEXTS.templateBasicRag)).toBeVisible(); await page.getByTestId("search-store-input").fill("YouTube QA"); await page.getByTestId("search-store-button").click(); - await page.getByText("YouTube QA").isVisible(); + await expect(page.getByText("YouTube QA")).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/features/store-shard-3.spec.ts b/src/frontend/tests/extended/features/store-shard-3.spec.ts index 3a6988b628..4983d59708 100644 --- a/src/frontend/tests/extended/features/store-shard-3.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-3.spec.ts @@ -1,23 +1,16 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "should order the visualization (requires store API key)", { tag: ["@release"] }, async ({ page, context }) => { test.skip(); - - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page, { skipModal: true }); await page.waitForTimeout(1000); @@ -40,7 +33,7 @@ test( await newPageStore.getByTestId("sidebar-nav-Langflow Store").click(); await newPageStore - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await newPageStore.getByTestId("api-key-save-button-store").click(); @@ -61,7 +54,7 @@ test( await newPageStore2.waitForTimeout(1000); - await expect(newPageStore2.getByText("Basic RAG")).toBeVisible({ + await expect(newPageStore2.getByText(TEXTS.templateBasicRag)).toBeVisible({ timeout: 30000, }); @@ -74,7 +67,7 @@ test( await newPageStore2.getByTestId("select-order-store").click(); await newPageStore2.getByText("Popular").click(); - await newPageStore2.getByText("Basic RAG").isVisible(); + await newPageStore2.getByText(TEXTS.templateBasicRag).isVisible(); }, ); @@ -83,13 +76,8 @@ test( { tag: ["@release"] }, async ({ page, context }) => { test.skip(); - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page, { skipModal: true }); await page.waitForSelector('[data-testid="button-store"]', { @@ -110,7 +98,7 @@ test( await newPageStore.getByTestId("sidebar-nav-Langflow Store").click(); await newPageStore - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await newPageStore.getByTestId("api-key-save-button-store").click(); await expect( @@ -179,8 +167,7 @@ test( .getByTestId("icon-ToyBrick") ?.count(); await newPageStore2.waitForTimeout(500); - if (iconGroupAllCount === 0 || toyBrickAllCount === 0) { - expect(false).toBe(true); - } + expect(iconGroupAllCount).not.toBe(0); + expect(toyBrickAllCount).not.toBe(0); }, ); diff --git a/src/frontend/tests/extended/features/tool-mode.spec.ts b/src/frontend/tests/extended/features/tool-mode.spec.ts index f91d3ac72c..f79b731135 100644 --- a/src/frontend/tests/extended/features/tool-mode.spec.ts +++ b/src/frontend/tests/extended/features/tool-mode.spec.ts @@ -1,15 +1,15 @@ 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 { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "User should be able to use components as tool", { tag: ["@release", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="disclosure-data sources"]', { timeout: 3000, state: "visible", @@ -35,79 +35,79 @@ test( await page.keyboard.press("ControlOrMeta+Shift+m"); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "visible", }); - expect(await page.getByText("toolset").count()).toBeGreaterThan(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBeGreaterThan(0); await page.keyboard.press("ControlOrMeta+Shift+m"); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "hidden", }); - expect(await page.getByText("toolset").count()).toBe(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBe(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "visible", }); - expect(await page.getByText("toolset").count()).toBeGreaterThan(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBeGreaterThan(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "hidden", }); - expect(await page.getByText("toolset").count()).toBe(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBe(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "visible", }); - expect(await page.getByText("toolset").count()).toBeGreaterThan(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBeGreaterThan(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "hidden", }); - expect(await page.getByText("toolset").count()).toBe(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBe(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "visible", }); - expect(await page.getByText("toolset").count()).toBeGreaterThan(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBeGreaterThan(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "hidden", }); - expect(await page.getByText("toolset").count()).toBe(0); + expect(await page.getByText(TEXTS.labelToolset).count()).toBe(0); await page.getByTestId("tool-mode-button").click(); - await page.waitForSelector("text=toolset", { + await page.waitForSelector(`text=${TEXTS.labelToolset}`, { timeout: 5000, state: "visible", }); @@ -141,7 +141,9 @@ test( expect(await page.locator(".react-flow__edge").count()).toBeGreaterThan(0); await page.getByTestId("button_run_url").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.getByTestId("output-inspection-toolset-urlcomponent").click(); @@ -152,7 +154,7 @@ test( ); expect(await page.getByTestId("tool_tags").count()).toBeGreaterThan(0); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.getByTestId("sidebar-custom-component-button").click(); @@ -171,7 +173,9 @@ test( await page.getByTestId("button_run_custom component").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page .getByTestId("output-inspection-toolset-customcomponent") diff --git a/src/frontend/tests/extended/features/twoEdges.spec.ts b/src/frontend/tests/extended/features/twoEdges.spec.ts index 30d59e0548..475b0d1953 100644 --- a/src/frontend/tests/extended/features/twoEdges.spec.ts +++ b/src/frontend/tests/extended/features/twoEdges.spec.ts @@ -1,6 +1,7 @@ -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to see multiple edges and interact with them", { tag: ["@release", "@api", "@workspace"] }, @@ -8,8 +9,15 @@ test( await awaitBootstrapTest(page); await page.getByText("Vector Store RAG", { exact: true }).last().click(); - await page.getByText("Retriever", { exact: true }).first().isVisible(); - await page.getByText("Search Results", { exact: true }).first().isVisible(); + // The post-Knowledge-merge Vector Store RAG template uses a single + // Knowledge node instead of separate Retriever / Search Results nodes, + // so assert against display_names that ARE in the current template. + await expect( + page.getByText("Knowledge", { exact: true }).first(), + ).toBeVisible(); + await expect( + page.getByText(TEXTS.componentLanguageModel, { exact: true }).first(), + ).toBeVisible(); await page.getByTestId("canvas_controls_dropdown").click(); const focusElementsOnBoard = async ({ page }) => { @@ -23,12 +31,14 @@ test( await focusElementsOnBoard({ page }); await page.getByTestId("canvas_controls_dropdown").click({ force: true }); - await page.getByText("Retriever", { exact: true }).first().isHidden(); - await page.getByTestId("icon-ChevronDown").last().isVisible(); + await page.getByText("Knowledge", { exact: true }).first().isHidden(); + await expect(page.getByTestId("icon-ChevronDown").last()).toBeVisible(); await page.getByTestId("icon-ChevronDown").last().click(); - await page.getByText("Retriever", { exact: true }).first().isVisible(); - await page.getByText("Search Results", { exact: true }).first().isVisible(); - - await page.getByTestId("icon-EyeOff").nth(0).isVisible(); + await expect( + page.getByText("Knowledge", { exact: true }).first(), + ).toBeVisible(); + await expect( + page.getByText(TEXTS.componentLanguageModel, { exact: true }).first(), + ).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/features/userSettings.spec.ts b/src/frontend/tests/extended/features/userSettings.spec.ts index 0c87c54ed3..b6acba2e67 100644 --- a/src/frontend/tests/extended/features/userSettings.spec.ts +++ b/src/frontend/tests/extended/features/userSettings.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test.beforeAll(async () => { await new Promise((resolve) => setTimeout(resolve, 10000)); }); @@ -26,7 +27,7 @@ test( }); await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").click(); + await page.getByText(TEXTS.settings).click(); // Wait for settings page to fully load await page @@ -85,15 +86,13 @@ test( skipModal: true, }); await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").click(); + await page.getByText(TEXTS.settings).click(); await page.getByText("Global Variables").click(); await expect( page.getByText("Global Variables", { exact: true }).nth(1), ).toBeVisible({ timeout: 10000 }); await page.getByText("Add New").click(); - await page - .getByPlaceholder("Enter a name for the variable...") - .fill(randomName); + await page.getByPlaceholder(TEXTS.placeholderVariableName).fill(randomName); await expect(page.getByText("Generic", { exact: true }).last()).toBeVisible( { timeout: 10000 }, ); @@ -128,13 +127,13 @@ test( await page.locator(`.ag-cell:has-text("${randomName}")`).first().click(); - await page.getByPlaceholder("Enter a name for the variable...").waitFor({ + await page.getByPlaceholder(TEXTS.placeholderVariableName).waitFor({ state: "visible", timeout: 30000, }); await page - .getByPlaceholder("Enter a name for the variable...") + .getByPlaceholder(TEXTS.placeholderVariableName) .fill(randomName2); await page @@ -151,13 +150,13 @@ test( await page.locator(`.ag-cell:has-text("${randomName2}")`).first().click(); - await page.getByPlaceholder("Enter a name for the variable...").waitFor({ + await page.getByPlaceholder(TEXTS.placeholderVariableName).waitFor({ state: "visible", timeout: 30000, }); await page - .getByPlaceholder("Enter a name for the variable...") + .getByPlaceholder(TEXTS.placeholderVariableName) .fill(randomName3); await page @@ -192,7 +191,7 @@ test("should see shortcuts", { tag: ["@release"] }, async ({ page }) => { }); await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").click(); + await page.getByText(TEXTS.settings).click(); // Wait for settings page to fully load await page @@ -238,7 +237,7 @@ test("should see shortcuts", { tag: ["@release"] }, async ({ page }) => { await expect(page.getByText("Changes Save", { exact: true })).toBeVisible({ timeout: 10000, }); - await expect(page.getByText("Delete", { exact: true })).toBeVisible({ + await expect(page.getByText(TEXTS.delete, { exact: true })).toBeVisible({ timeout: 10000, }); await expect(page.getByText("Open Playground", { exact: true })).toBeVisible({ @@ -317,7 +316,7 @@ test( skipModal: true, }); await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings").click(); + await page.getByText(TEXTS.settings).click(); // Wait for settings page to fully load await page @@ -369,7 +368,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, diff --git a/src/frontend/tests/extended/features/validate-raise-errors-components.spec.ts b/src/frontend/tests/extended/features/validate-raise-errors-components.spec.ts index 8806c3c7ea..bd13eaa669 100644 --- a/src/frontend/tests/extended/features/validate-raise-errors-components.spec.ts +++ b/src/frontend/tests/extended/features/validate-raise-errors-components.spec.ts @@ -1,10 +1,11 @@ import { expect, test } from "../../fixtures"; import { addCustomComponent } from "../../utils/add-custom-component"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { zoomOut } from "../../utils/zoom-out"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to see errors on popups when raise an error", { tag: ["@release", "@workspace", "@components"] }, @@ -15,7 +16,6 @@ from langflow.custom import Component from langflow.io import MessageTextInput, Output from langflow.schema import Data - class CustomComponent(Component): display_name = "Custom Component" description = "Use as a template to create your own component." @@ -44,9 +44,7 @@ class CustomComponent(Component): self.status = data return data `; - - await awaitBootstrapTest(page); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector( '[data-testid="sidebar-custom-component-button"]', @@ -73,7 +71,7 @@ class CustomComponent(Component): .locator("textarea") .fill(customComponentCodeWithRaiseErrorMessage); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); await page.getByTestId("button_run_custom component").click(); diff --git a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts index ad9a469c06..7e2cbd8e07 100644 --- a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts +++ b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts @@ -1,28 +1,24 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; import { zoomOut } from "../../utils/zoom-out"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to see output inspection", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await adjustScreenView(page); await initialGPTsetup(page); @@ -31,7 +27,7 @@ test( await page.waitForTimeout(600); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); @@ -57,7 +53,7 @@ test( // Add URL component await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchUrl); await page.waitForSelector('[data-testid="data_sourceURL"]', { timeout: 3000, }); @@ -78,7 +74,7 @@ test( // Add two chat outputs await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 1000, }); @@ -121,7 +117,7 @@ test( // Run flow and test text output inspection await page.getByTestId("button_run_url").first().click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); await page.keyboard.press("o"); @@ -129,10 +125,10 @@ test( exact: true, }); - await page.getByText(`Component Output`, { + await page.getByText(TEXTS.componentOutput, { exact: true, }); - await page.getByText("Close").first().click(); + await page.getByText(TEXTS.close).first().click(); await page .getByTestId("handle-urlcomponent-shownode-extracted pages-right") .click(); @@ -144,7 +140,7 @@ test( // Run and verify text output is still shown await page.getByTestId("button_run_url").first().click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); @@ -165,10 +161,10 @@ test( exact: true, }); - await page.getByText(`Component Output`, { + await page.getByText(TEXTS.componentOutput, { exact: true, }); - await page.getByText("Close").first().click(); + await page.getByText(TEXTS.close).first().click(); await page.waitForTimeout(600); await page @@ -183,7 +179,7 @@ test( // Run and verify dataframe output is now shown await page.getByTestId("button_run_url").first().click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); await page.waitForTimeout(600); @@ -194,10 +190,10 @@ test( exact: true, }); - await page.getByText(`Component Output`, { + await page.getByText(TEXTS.componentOutput, { exact: true, }); - await page.getByText("Close").first().click(); + await page.getByText(TEXTS.close).first().click(); await page.waitForTimeout(600); // Remove all connections const dataEdge = await page.locator(".react-flow__edge").first(); @@ -208,7 +204,7 @@ test( // Run and verify data output is shown await page.getByTestId("button_run_url").first().click(); - await page.waitForSelector("text=built successfully", { + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { timeout: 30000 * 3, }); await page.waitForTimeout(600); @@ -217,12 +213,12 @@ test( exact: true, }); - await page.getByText(`Component Output`, { + await page.getByText(TEXTS.componentOutput, { exact: true, }); const closeButton = await page - .getByText(`Close`, { + .getByText(TEXTS.close, { exact: true, }) .count(); diff --git a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-2.spec.ts b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-2.spec.ts index 1ca9835187..96668fcbf7 100644 --- a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-2.spec.ts +++ b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-2.spec.ts @@ -1,5 +1,3 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; @@ -10,28 +8,29 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; + +import { TEXTS } from "../../utils/constants/texts"; test( "user must interact with chat with Input/Output", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); // Open Playground - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, @@ -67,7 +66,7 @@ test( await page.getByTestId("playground-close-button").click(); await disableInspectPanel(page); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await openAdvancedOptions(page); await page.getByTestId("showsender_name").click(); await closeAdvancedOptions(page); @@ -86,7 +85,9 @@ test( .nth(1) .fill("TestSenderNameAI"); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="button-send"]', { timeout: 100000, diff --git a/src/frontend/tests/extended/integrations/duckduckgo.spec.ts b/src/frontend/tests/extended/integrations/duckduckgo.spec.ts index 145f1c57f1..b222be8bd4 100644 --- a/src/frontend/tests/extended/integrations/duckduckgo.spec.ts +++ b/src/frontend/tests/extended/integrations/duckduckgo.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should be able to use duckduckgo search component", { tag: ["@release", "@components"] }, @@ -36,7 +37,9 @@ test( await page.getByTestId("button_run_duckduckgo search").click(); const result = await Promise.race([ - page.waitForSelector("text=built successfully", { timeout: 30000 }), + page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }), page.waitForSelector("text=ratelimit", { timeout: 30000 }), ]); @@ -53,11 +56,15 @@ test( if (isBuiltSuccessfully) { await page.getByRole("gridcell").first().click(); - const searchResults = await page.getByPlaceholder("Empty").inputValue(); + const searchResults = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .inputValue(); expect(searchResults.length).toBeGreaterThan(10); expect(searchResults.toLowerCase()).toContain("langflow"); } else { - const value = await page.getByPlaceholder("Empty").inputValue(); + const value = await page + .getByPlaceholder(TEXTS.placeholderEmpty) + .inputValue(); expect(value.length).toBeGreaterThan(10); expect(value.toLowerCase()).toContain("ratelimit"); } diff --git a/src/frontend/tests/extended/integrations/youtube-transcripts.spec.ts b/src/frontend/tests/extended/integrations/youtube-transcripts.spec.ts deleted file mode 100644 index c290c7d045..0000000000 --- a/src/frontend/tests/extended/integrations/youtube-transcripts.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -test.skip( - "user should be able to use youtube transcripts component", - { tag: ["@release", "@components"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("youtube"); - - await page.getByTestId("youtubeYouTube Transcripts").hover(); - await page.getByTestId("add-component-button-youtube-transcripts").click(); - await adjustScreenView(page); - - let outdatedComponents = await page.getByTestId("update-button").count(); - - while (outdatedComponents > 0) { - await page.getByTestId("update-button").first().click(); - outdatedComponents = await page.getByTestId("update-button").count(); - } - - await page - .getByTestId("textarea_str_url") - .fill("https://www.youtube.com/watch?v=VqhCQZaH4Vs"); - - await adjustScreenView(page); - - await page.getByTestId("button_run_youtube transcripts").click(); - - await page.waitForSelector("text=built successfully", { timeout: 3000 }); - - await page - .getByTestId("output-inspection-transcript-youtube-transcripts") - .first() - .click(); - await page.waitForSelector("text=Component Output", { timeout: 3000 }); - await page.getByRole("gridcell").first().click(); - const value = await page.getByPlaceholder("Empty").inputValue(); - expect(value.length).toBeGreaterThan(10); - }, -); diff --git a/src/frontend/tests/extended/regression/general-bugs-agent-anthropic-integration.spec.ts b/src/frontend/tests/extended/regression/general-bugs-agent-anthropic-integration.spec.ts index e104f3e3c9..92cb1c550a 100644 --- a/src/frontend/tests/extended/regression/general-bugs-agent-anthropic-integration.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-agent-anthropic-integration.spec.ts @@ -1,49 +1,35 @@ -import dotenv from "dotenv"; -import { readFileSync } from "fs"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; test( - "user must be able to send images in the playground with the agent component", + "user must be able to run Simple Agent in the playground with Anthropic provider", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.ANTHROPIC_API_KEY, - "ANTHROPIC_API_KEY required to run this test", - ); + skipIfMissing.anthropicKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await openStarterProject(page, "Simple Agent"); await page.getByTestId("value-dropdown-dropdown_str_agent_llm").click(); - - await page.waitForTimeout(200); - await page.getByText("Anthropic").last().click(); await page - .getByTestId("popover-anchor-input-api_key") + .getByTestId(TID.popoverAnchorInputApiKey) .fill(process.env.ANTHROPIC_API_KEY || ""); - await page.getByTestId("playground-btn-flow-io").click(); + await page.getByTestId(TID.playgroundBtnFlowIo).click(); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 100000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.componentMount, }); - await page.getByTestId("button-send").click(); + await page.getByTestId(TID.buttonSend).click(); - await page.waitForSelector("text=Finished", { - timeout: 5000, - }); - - await page.waitForTimeout(2000); + await page.waitForSelector("text=Finished", { timeout: TIMEOUTS.short }); const textFromLlm = await page .locator(".markdown.prose") diff --git a/src/frontend/tests/extended/regression/general-bugs-agent-images-playground.spec.ts b/src/frontend/tests/extended/regression/general-bugs-agent-images-playground.spec.ts index a1206eefbc..8706ac13ae 100644 --- a/src/frontend/tests/extended/regression/general-bugs-agent-images-playground.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-agent-images-playground.spec.ts @@ -1,37 +1,28 @@ -import dotenv from "dotenv"; import { readFileSync } from "fs"; -import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TID } from "../../utils/constants/testIds"; +import { TIMEOUTS } from "../../utils/constants/timeouts"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { openStarterProject } from "../../utils/flow/open-starter-project"; test( "user must be able to send images in the playground with the agent component", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.ANTHROPIC_API_KEY, - "ANTHROPIC_API_KEY required to run this test", - ); + skipIfMissing.anthropicKey(); + loadDotenvIfLocal(__dirname); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await openStarterProject(page, "Simple Agent"); await page.getByTestId("value-dropdown-dropdown_str_agent_llm").click(); - - await page.waitForTimeout(200); - await page.getByText("Anthropic").last().click(); await page - .getByTestId("popover-anchor-input-api_key") + .getByTestId(TID.popoverAnchorInputApiKey) .fill(process.env.ANTHROPIC_API_KEY || ""); - await page.getByTestId("playground-btn-flow-io").click(); + await page.getByTestId(TID.playgroundBtnFlowIo).click(); // Read the image file as a binary string const filePath = "tests/assets/chain.png"; @@ -54,29 +45,26 @@ test( { fileContent }, ); - await page.waitForSelector('[data-testid="input-chat-playground"]', { - timeout: 100000, + await page.waitForSelector(`[data-testid="${TID.inputChatPlayground}"]`, { + timeout: TIMEOUTS.componentMount, }); - // Locate the target element - const element = await page.getByTestId("input-chat-playground"); - - // Dispatch the drop event on the target element + const element = await page.getByTestId(TID.inputChatPlayground); await element.dispatchEvent("drop", { dataTransfer }); - await page.getByTestId("input-chat-playground").fill("what is this image?"); + await page.getByTestId(TID.inputChatPlayground).fill("what is this image?"); - await page.waitForSelector('[data-testid="button-send"]', { - timeout: 100000, + await page.waitForSelector(`[data-testid="${TID.buttonSend}"]`, { + timeout: TIMEOUTS.componentMount, }); - await page.getByTestId("button-send").click(); + await page.getByTestId(TID.buttonSend).click(); - await page.waitForSelector("text=chain.png", { timeout: 30000 }); + await page.waitForSelector("text=chain.png", { + timeout: TIMEOUTS.standard, + }); - await page.getByText("chain.png").isVisible(); - - await page.waitForTimeout(5000); + await expect(page.getByText("chain.png")).toBeVisible(); const textFromLlm = await page .locator(".markdown.prose") diff --git a/src/frontend/tests/extended/regression/general-bugs-agent-sum-duplicate-message-playground.spec.ts b/src/frontend/tests/extended/regression/general-bugs-agent-sum-duplicate-message-playground.spec.ts index 750fd73753..b93f5f539f 100644 --- a/src/frontend/tests/extended/regression/general-bugs-agent-sum-duplicate-message-playground.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-agent-sum-duplicate-message-playground.spec.ts @@ -1,25 +1,23 @@ -import dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { selectAnthropicModel } from "../../utils/select-anthropic-model"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must not experience message duplication in mathematical expressions with agent component", { tag: ["@release", "@components", "@workspace"] }, async ({ page }) => { - test.skip( - !process?.env?.ANTHROPIC_API_KEY, - "ANTHROPIC_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.anthropicKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Simple Agent" }).first().click(); + await page + .getByRole("heading", { name: TEXTS.templateSimpleAgent }) + .first() + .click(); await selectAnthropicModel(page); diff --git a/src/frontend/tests/extended/regression/general-bugs-component-as-tool-shortcut.spec.ts b/src/frontend/tests/extended/regression/general-bugs-component-as-tool-shortcut.spec.ts index ba1d5a7934..d74e8b3e9f 100644 --- a/src/frontend/tests/extended/regression/general-bugs-component-as-tool-shortcut.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-component-as-tool-shortcut.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { extractAndCleanCode } from "../../utils/extract-and-clean-code"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to use component as tool shortcut only if has tool mode is True", { tag: ["@release", "@components"] }, @@ -15,7 +16,7 @@ test( }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("prompt"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchPrompt); await page .getByTestId("models_and_agentsPrompt Template") diff --git a/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts b/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts index 2a5439582b..e50ac624fc 100644 --- a/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-component-webhook-api-key-display.spec.ts @@ -9,6 +9,9 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; + +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to see api key in webhook component when auto login is disabled", { tag: ["@release"] }, @@ -77,7 +80,7 @@ test( expect(curl).toContain("x-api-key"); - await page.getByText("Close", { exact: true }).last().click(); + await page.getByText(TEXTS.close, { exact: true }).last().click(); await closeAdvancedOptions(page); @@ -103,13 +106,7 @@ test( }, }); }); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("webhook"); @@ -141,7 +138,7 @@ test( expect(curl).not.toContain("x-api-key"); - await page.getByText("Close", { exact: true }).last().click(); + await page.getByText(TEXTS.close, { exact: true }).last().click(); await closeAdvancedOptions(page); diff --git a/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts b/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts index 2f398875d9..aa7246c77b 100644 --- a/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-delete-handle-advanced-input.spec.ts @@ -8,6 +8,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { TEXTS } from "../../utils/constants/texts"; test( "the system must delete the handles from advanced fields when the code is updated", { tag: ["@release", "@components"] }, @@ -40,7 +41,7 @@ test( await closeAdvancedOptions(page); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); await page.waitForSelector('[data-testid="input_outputText Input"]', { timeout: 2000, }); diff --git a/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts b/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts index aac55443f2..a867250653 100644 --- a/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-frontend-crashing-on-invalid-replace.spec.ts @@ -1,19 +1,14 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to use a component with undefined replacement", { tag: ["@release"], }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-custom-component-button").click(); @@ -27,7 +22,6 @@ from lfx.custom.custom_component.component import Component from lfx.io import MessageTextInput, Output from lfx.schema.data import Data - class CustomComponent(Component): display_name = "Custom Component" description = "Use as a template to create your own component." @@ -60,7 +54,7 @@ class CustomComponent(Component): await page.keyboard.press(`ControlOrMeta+A`); await page.locator("textarea").fill(problematicCode); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); await page.waitForTimeout(1000); await page.waitForSelector("text=No direct replacement", { diff --git a/src/frontend/tests/extended/regression/general-bugs-hidden-input-edges.spec.ts b/src/frontend/tests/extended/regression/general-bugs-hidden-input-edges.spec.ts index a747e02cd6..bc7db94fa5 100644 --- a/src/frontend/tests/extended/regression/general-bugs-hidden-input-edges.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-hidden-input-edges.spec.ts @@ -1,5 +1,3 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; @@ -11,6 +9,7 @@ import { } from "../../utils/open-advanced-options"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { TEXTS } from "../../utils/constants/texts"; test( "user should not be able to hide connected inputs", { tag: ["@release", "@api", "@database"] }, @@ -18,13 +17,15 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector("text=Language Model", { timeout: 30000 }); await page .getByTestId("div-generic-node") - .getByText("Language Model", { exact: true }) + .getByText(TEXTS.componentLanguageModel, { exact: true }) .click(); await openAdvancedOptions(page); @@ -43,7 +44,7 @@ test( await page .getByTestId("div-generic-node") - .getByText("Language Model", { exact: true }) + .getByText(TEXTS.componentLanguageModel, { exact: true }) .click(); await openAdvancedOptions(page); @@ -68,7 +69,9 @@ test( await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await disableInspectPanel(page); @@ -76,7 +79,7 @@ test( await page .getByTestId("div-generic-node") - .getByText("Language Model", { exact: true }) + .getByText(TEXTS.componentLanguageModel, { exact: true }) .click(); await openAdvancedOptions(page); @@ -101,7 +104,7 @@ test( await page .getByTestId("div-generic-node") - .getByText("Language Model", { exact: true }) + .getByText(TEXTS.componentLanguageModel, { exact: true }) .click(); await openAdvancedOptions(page); diff --git a/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts b/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts index e4927ebdd7..7d6e07b2cc 100644 --- a/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts @@ -3,6 +3,7 @@ import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { TEXTS } from "../../utils/constants/texts"; async function toggleNodeState(page: Page, action: "minimize" | "expand") { const expectedCount = action === "minimize" ? 1 : 0; await page.getByTestId("more-options-modal").click(); @@ -20,7 +21,7 @@ test( await page.getByTestId("blank-flow").click(); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page .getByTestId("input_outputText Output") diff --git a/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts b/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts index 1fad98f333..9224fd5e33 100644 --- a/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts @@ -2,6 +2,7 @@ import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; test("user must be able to move flow from folder", async ({ page }) => { /* This is the original, happy-path regression: the destination * project is BRAND NEW and has never been observed by React Query, @@ -13,7 +14,9 @@ test("user must be able to move flow from folder", async ({ page }) => { await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForTimeout(2000); @@ -76,7 +79,9 @@ test("moved flow must appear when destination project was visited while still em // Create a flow and rename it so we can find it later by a unique name. await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForTimeout(2000); await renameFlow(page, { flowName }); await page.waitForTimeout(2000); diff --git a/src/frontend/tests/extended/regression/general-bugs-reset-flow-run.spec.ts b/src/frontend/tests/extended/regression/general-bugs-reset-flow-run.spec.ts index 170c498840..7240ea62ac 100644 --- a/src/frontend/tests/extended/regression/general-bugs-reset-flow-run.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-reset-flow-run.spec.ts @@ -4,19 +4,15 @@ import path from "path"; import { expect, test } from "../../fixtures"; import { addLegacyComponents } from "../../utils/add-legacy-components"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { zoomOut } from "../../utils/zoom-out"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "user can run flow with If-Else component multiple times with different branches", { tag: ["@release", "@components"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await addLegacyComponents(page); @@ -39,7 +35,7 @@ test( //---------------------------------- Text Output await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page.waitForSelector('[data-testid="input_outputText Output"]', { timeout: 100000, }); @@ -54,7 +50,7 @@ test( //---------------------------------- Text Output await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page.waitForSelector('[data-testid="input_outputText Output"]', { timeout: 100000, }); @@ -108,7 +104,9 @@ test( await page.getByTestId("button_run_text output").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.waitForTimeout(3000); @@ -134,7 +132,9 @@ test( await page.waitForTimeout(2000); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.waitForTimeout(3000); @@ -162,7 +162,9 @@ test( await page.waitForTimeout(2000); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.waitForTimeout(3000); @@ -186,7 +188,9 @@ test( await page.getByTestId("popover-anchor-input-input_text").fill("2"); await page.getByTestId("button_run_textoutputfalse").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); numberOfSuccessfullComponentsRun = 0; numberOfInactiveComponentsRun = 0; diff --git a/src/frontend/tests/extended/regression/general-bugs-save-changes-on-node.spec.ts b/src/frontend/tests/extended/regression/general-bugs-save-changes-on-node.spec.ts index 1ccf2dbd65..3d0b07bde7 100644 --- a/src/frontend/tests/extended/regression/general-bugs-save-changes-on-node.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-save-changes-on-node.spec.ts @@ -4,6 +4,7 @@ import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { renameFlow } from "../../utils/rename-flow"; +import { TEXTS } from "../../utils/constants/texts"; async function verifyTextareaValue( page: Page, value: string, @@ -57,7 +58,7 @@ test( await renameFlow(page, { flowName: randomFlowName }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("text output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextOutput); await page .getByTestId("input_outputText Output") diff --git a/src/frontend/tests/extended/regression/general-bugs-shard-3836.spec.ts b/src/frontend/tests/extended/regression/general-bugs-shard-3836.spec.ts index 3bbb93f279..294bf6517f 100644 --- a/src/frontend/tests/extended/regression/general-bugs-shard-3836.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-shard-3836.spec.ts @@ -1,5 +1,3 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; @@ -8,29 +6,27 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; import { uploadFile } from "../../utils/upload-file"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to send an image on chat using advanced tool on ChatInputComponent", { tag: ["@release", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); await page.waitForSelector("text=Chat Input", { timeout: 30000 }); - await page.getByText("Chat Input", { exact: true }).click(); + await page.getByText(TEXTS.componentChatInput, { exact: true }).click(); await openAdvancedOptions(page); await page.getByTestId("showfiles").click(); await closeAdvancedOptions(page); @@ -50,7 +46,9 @@ test( await page.getByTestId("button_run_chat output").click(); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="button-send"]', { timeout: 100000, diff --git a/src/frontend/tests/extended/regression/general-bugs-shard-3909.spec.ts b/src/frontend/tests/extended/regression/general-bugs-shard-3909.spec.ts index 68014ca3e5..518194d7d4 100644 --- a/src/frontend/tests/extended/regression/general-bugs-shard-3909.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-shard-3909.spec.ts @@ -1,28 +1,22 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "user must be able to create a new flow clicking on New Flow button", { tag: ["@release", "@mainpage"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); - await page.getByText("Close").last().click(); + await page.getByText(TEXTS.close).last().click(); await page.getByTestId("add-project-button").click(); - await page.getByText("New Project").last().click(); + await page.getByText(TEXTS.labelNewProject).last().click(); await page.waitForSelector("text=new flow", { timeout: 30000 }); @@ -44,7 +38,9 @@ test( await page.getByTestId("new_project_btn_empty_page").click(); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector("text=playground", { timeout: 30000 }); await page.waitForSelector("text=share", { timeout: 30000 }); diff --git a/src/frontend/tests/extended/regression/general-bugs-truncate-results.spec.ts b/src/frontend/tests/extended/regression/general-bugs-truncate-results.spec.ts deleted file mode 100644 index 7b369170c9..0000000000 --- a/src/frontend/tests/extended/regression/general-bugs-truncate-results.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; - -// TODO: This test needs to be rebuilt/refactored -test.skip( - "truncated values must be displayed correctly", - { tag: ["@release", "@components"] }, - async ({ page }) => { - await awaitBootstrapTest(page); - - await page.getByTestId("blank-flow").click(); - - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("url"); - await page.waitForSelector('[data-testid="data_sourceURL"]', { - timeout: 1000, - }); - - await page - .getByTestId("data_sourceURL") - .dragTo(page.locator('//*[@id="react-flow-id"]'), { - targetPosition: { x: 300, y: 300 }, - }); - - await page.getByTestId("input-list-plus-btn_urls-0").click(); - - await page - .getByTestId("inputlist_str_urls_0") - .fill("https://docs.langflow.org/"); - - await page - .getByTestId("inputlist_str_urls_1") - .fill("https://www.langflow.org/"); - await adjustScreenView(page); - - await page.getByTestId("default_slider_display_value").click(); - await page.getByTestId("slider_input").fill("5"); - - await page.getByTestId("button_run_url").click(); - - await page.waitForSelector("text=built successfully", { - timeout: 60000 * 3, - }); - - await page - .getByTestId("output-inspection-extracted pages-urlcomponent") - .click(); - - await page.getByText(`Inspect the output of the component below.`, { - exact: true, - }); - - await page.waitForSelector("text=truncated", { - timeout: 3000, - }); - - const trucatedWordCount = await page.getByText(`[truncated`).count(); - expect(trucatedWordCount).toBeGreaterThan(0); - - expect(page.locator("span.ag-header-cell-text").nth(1)).toHaveText("url"); - - expect(page.locator("span[data-ref=lbRecordCount]").first()).toHaveText( - "100", - ); - }, -); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts index 5d95c0bc42..2efa566eb6 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts @@ -1,33 +1,33 @@ -import * as dotenv from "dotenv"; -import path from "path"; -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "should delete rows from table message", { tag: ["@release"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await initialGPTsetup(page); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.getByTestId("user-profile-settings").click(); await page.waitForSelector('text="Settings"'); - await page.getByText("Settings").last().click(); + await page.getByText(TEXTS.settings).last().click(); await page.waitForSelector('text="Messages"'); await page.getByText("Messages").last().click(); @@ -39,6 +39,6 @@ test( await page.getByTestId("icon-Trash2").first().click(); await page.waitForSelector("text=No Data Available", { timeout: 30000 }); - await page.getByText("No Data Available").isVisible(); + await expect(page.getByText("No Data Available")).toBeVisible(); }, ); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts index 6ca48da45e..8279ebe261 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts @@ -1,37 +1,33 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "freeze must work correctly", { tag: ["@release", "@api", "@components"] }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); const promptText = "answer as you are a dog"; const newPromptText = "answer as you are a bird"; await awaitBootstrapTest(page); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 100000, }); await adjustScreenView(page); - await page.getByText("Language Model").last().click(); + await page.getByText(TEXTS.componentLanguageModel).last().click(); await page.keyboard.press("Delete"); //connection 1 @@ -54,13 +50,13 @@ test( await page.getByTestId("modal-promptarea_prompt_template").fill(promptText); - await page.getByText("Check & Save").click(); + await page.getByText(TEXTS.checkAndSave).click(); await initialGPTsetup(page); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully"); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`); await page.getByTestId("playground-btn-flow-io").click(); @@ -81,7 +77,7 @@ test( // Ensure we captured a non-empty response expect(firstResponseText.length).toBeGreaterThan(0); - // await page.getByText("Close").last().click(); + // await page.getByText(TEXTS.close).last().click(); await page.getByTestId("playground-close-button").click(); // Freeze the Chat Output node (not Prompt) so the entire response is cached @@ -113,13 +109,15 @@ test( .getByTestId("modal-promptarea_prompt_template") .fill(newPromptText); - await page.getByText("Check & Save").click(); + await page.getByText(TEXTS.checkAndSave).click(); await page.waitForTimeout(500); await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: 30000, + }); await page.getByTestId("playground-btn-flow-io").click(); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-11.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-11.spec.ts index 712e0686a4..d455ae0869 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-11.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-11.spec.ts @@ -2,17 +2,13 @@ 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 { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user should be able to use ComposIO without getting api_key error", { tag: ["@release"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.getByTestId("sidebar-search-input").click(); await page.getByTestId("sidebar-search-input").fill("composio"); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts index 3061ecef68..4c84259f27 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts @@ -1,34 +1,28 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { TEXTS } from "../../utils/constants/texts"; test( "should be able to share a component on the store by clicking on the share button on the canvas (requires store API key)", { tag: ["@release", "@api"] }, async ({ page }) => { - test.skip( - !process?.env?.STORE_API_KEY, - "STORE_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); await awaitBootstrapTest(page); - await page.getByText("Close", { exact: true }).click(); + await page.getByText(TEXTS.close, { exact: true }).click(); await page.waitForSelector('[data-testid="user-profile-settings"]', { timeout: 3000, }); await page.getByTestId("user-profile-settings").click(); - await page.getByText("Settings", { exact: true }).first().click(); + await page.getByText(TEXTS.settings, { exact: true }).first().click(); await page - .getByPlaceholder("Insert your API Key") + .getByPlaceholder(TEXTS.placeholderApiKey) .fill(process.env.STORE_API_KEY ?? ""); await page.getByTestId("api-key-save-button-store").click(); @@ -50,7 +44,9 @@ test( await page.getByTestId("new-project-btn").click(); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.waitForSelector("text=share", { timeout: 10000 }); await page.waitForSelector("text=playground", { timeout: 10000 }); await page.waitForSelector("text=api", { timeout: 10000 }); diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts index c0520a6d8b..fe4f1b23a1 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts @@ -1,36 +1,25 @@ -import * as dotenv from "dotenv"; -import path from "path"; import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { initialGPTsetup } from "../../utils/initialGPTsetup"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "should copy code from playground modal", { tag: ["@release"], }, async ({ page }) => { - test.skip( - !process?.env?.OPENAI_API_KEY, - "OPENAI_API_KEY required to run this test", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); + await openBlankFlow(page); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 30000, }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page .getByTestId("input_outputChat Output") @@ -39,7 +28,7 @@ test( }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat input"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatInput); await page .getByTestId("input_outputChat Input") @@ -48,7 +37,9 @@ test( }); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("openai"); + await page + .getByTestId("sidebar-search-input") + .fill(TEXTS.providerOpenAiSearch); await page .getByTestId("openaiOpenAI") @@ -91,7 +82,9 @@ test( .click(); await adjustScreenView(page); - await page.getByRole("button", { name: "Playground", exact: true }).click(); + await page + .getByRole("button", { name: TEXTS.playground, exact: true }) + .click(); await page.waitForSelector('[data-testid="input-chat-playground"]', { timeout: 100000, }); @@ -132,20 +125,14 @@ test( "playground button should be enabled or disabled", { tag: ["@release", "@api", "@workspace"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); expect(await page.getByTestId("playground-btn-flow").isDisabled()); expect(await page.getByText("Langflow Chat").isHidden()); await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("chat output"); + await page.getByTestId("sidebar-search-input").fill(TEXTS.searchChatOutput); await page.waitForSelector('[data-testid="input_outputChat Output"]', { timeout: 30000, diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts index 8e8cf67ae2..8a04c70667 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; +import { TEXTS } from "../../utils/constants/texts"; test( "should be able to see error when something goes wrong on Code Modal", { tag: ["@release"] }, @@ -13,14 +14,7 @@ test( testInfo.project.name.includes("win") || process.platform === "win32", "Import error detection differs on Windows due to C-extension handling", ); - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector( '[data-testid="sidebar-custom-component-button"]', @@ -65,7 +59,7 @@ class CustomComponent(Component): await page.locator("textarea").press("Control+a"); await page.locator("textarea").fill(customCodeWithError); - await page.getByText("Check & Save").last().click(); + await page.getByText(TEXTS.checkAndSave).last().click(); // Wait for the error message to appear and have sufficient length await page.waitForFunction( diff --git a/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts b/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts index 06d8a8c3b8..f977704edb 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-7.spec.ts @@ -1,21 +1,15 @@ 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 { openBlankFlow } from "../../utils/flow/open-blank-flow"; // TODO: This test might not be needed anymore test( "should be able to select all with ctrl + A on advanced modal", { tag: ["@release"] }, async ({ page }) => { - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 10000, - }); - - await page.getByTestId("blank-flow").click(); + await openBlankFlow(page); await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', { timeout: 3000, diff --git a/src/frontend/tests/fixtures.ts b/src/frontend/tests/fixtures.ts index 6508208b1f..cb6fa6abc9 100644 --- a/src/frontend/tests/fixtures.ts +++ b/src/frontend/tests/fixtures.ts @@ -2,6 +2,8 @@ import { test as base, expect, Page } from "@playwright/test"; import "./playwrightCoverage"; +export type { LangflowPage } from "./utils/types"; + // Optional CPU throttling for reproducing race conditions seen on slower // runners (Windows CI). Enable with LF_CPU_THROTTLE=, e.g. 4. const CPU_THROTTLE_RATE = (() => { @@ -36,7 +38,7 @@ export const test = base.extend({ // Flag to allow flow errors (for tests that expect errors) let allowFlowErrors = false; - // Add helper method to page context + // Add helper method to page context — see LangflowPage type in utils/types.ts (page as Page & { allowFlowErrors?: () => void }).allowFlowErrors = () => { allowFlowErrors = true; }; @@ -241,12 +243,6 @@ export const test = base.extend({ // Check for errors and fail test if not allowed if (errors.length > 0) { const flowErrors = errors.filter((e) => e.type === "flow_error"); - const httpErrors = errors.filter((e) => e.type === "http_error"); - - if (flowErrors.length > 0) { - } - if (httpErrors.length > 0) { - } // Fail the test if flow errors occurred and weren't allowed if (flowErrors.length > 0 && !allowFlowErrors) { diff --git a/src/frontend/tests/templates/unit-test-components.spec.ts b/src/frontend/tests/templates/unit-test-components.spec.ts deleted file mode 100644 index c226264c0a..0000000000 --- a/src/frontend/tests/templates/unit-test-components.spec.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { test } from "../fixtures"; - -test("your_test_name", async ({ page }) => {}); diff --git a/src/frontend/tests/utils/add-flow-to-test-on-empty-langflow.ts b/src/frontend/tests/utils/add-flow-to-test-on-empty-langflow.ts index c4e19b7ec8..f80f9a997b 100644 --- a/src/frontend/tests/utils/add-flow-to-test-on-empty-langflow.ts +++ b/src/frontend/tests/utils/add-flow-to-test-on-empty-langflow.ts @@ -1,8 +1,11 @@ import type { Page } from "@playwright/test"; +import { TEXTS } from "../utils/constants/texts"; export const addFlowToTestOnEmptyLangflow = async (page: Page) => { await page.getByTestId("new_project_btn_empty_page").click(); await page.getByTestId("side_nav_options_all-templates").click(); - await page.getByRole("heading", { name: "Basic Prompting" }).click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); await page.getByTestId("icon-ChevronLeft").click(); }; diff --git a/src/frontend/tests/utils/add-new-api-keys.ts b/src/frontend/tests/utils/add-new-api-keys.ts deleted file mode 100644 index 70508e01a1..0000000000 --- a/src/frontend/tests/utils/add-new-api-keys.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Page } from "@playwright/test"; - -export async function addNewApiKeys(page: Page) { - const apiKeyInput = page.getByTestId("popover-anchor-input-api_key"); - const openaiApiKeyInput = page.getByTestId( - "popover-anchor-input-openai_api_key", - ); - - const isApiKeyInputVisible = await apiKeyInput.count(); - const isOpenaiApiKeyInputVisible = await openaiApiKeyInput.count(); - - if (isApiKeyInputVisible > 0) { - for (let i = 0; i < isApiKeyInputVisible; i++) { - await apiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? ""); - } - } - - if (isOpenaiApiKeyInputVisible > 0) { - for (let i = 0; i < isOpenaiApiKeyInputVisible; i++) { - await openaiApiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? ""); - } - } -} diff --git a/src/frontend/tests/utils/add-new-user-and-loggin.ts b/src/frontend/tests/utils/add-new-user-and-loggin.ts index ae490a1660..588c478960 100644 --- a/src/frontend/tests/utils/add-new-user-and-loggin.ts +++ b/src/frontend/tests/utils/add-new-user-and-loggin.ts @@ -1,6 +1,7 @@ import { type Page } from "@playwright/test"; import { expect } from "../fixtures"; +import { TEXTS } from "../utils/constants/texts"; export const addNewUserAndLogin = async (page: Page) => { await page.route("**/api/v1/auto_login", (route) => { route.fulfill({ @@ -31,16 +32,22 @@ export const addNewUserAndLogin = async (page: Page) => { await page.goto("/"); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill("langflow"); - await page.getByPlaceholder("Password").fill("langflow"); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .fill(TEXTS.authDefaultCredential); + await page + .getByPlaceholder(TEXTS.placeholderPassword) + .fill(TEXTS.authDefaultCredential); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); // Wait for any loading text to disappear before checking the homepage: // mainpage_title only renders after the homepage data finishes loading, @@ -66,7 +73,10 @@ export const addNewUserAndLogin = async (page: Page) => { //CRUD an user await page.getByText("New User", { exact: true }).click(); - await page.getByPlaceholder("Username").last().fill(randomName); + await page + .getByPlaceholder(TEXTS.placeholderUsername) + .last() + .fill(randomName); await page.locator('input[name="password"]').fill(randomPassword); await page.locator('input[name="confirmpassword"]').fill(randomPassword); @@ -76,7 +86,7 @@ export const addNewUserAndLogin = async (page: Page) => { await page.locator("#is_active").click(); - await page.getByText("Save", { exact: true }).click(); + await page.getByText(TEXTS.save, { exact: true }).click(); await page.waitForSelector("text=new user added", { timeout: 30000 }); @@ -94,18 +104,20 @@ export const addNewUserAndLogin = async (page: Page) => { sessionStorage.setItem("testMockAutoLogin", "true"); }); - await page.getByText("Logout", { exact: true }).click(); + await page.getByText(TEXTS.logout, { exact: true }).click(); - await page.waitForSelector("text=sign in to langflow", { timeout: 30000 }); + await page.waitForSelector(`text=${TEXTS.authSignInHeader}`, { + timeout: 30000, + }); - await page.getByPlaceholder("Username").fill(randomName); - await page.getByPlaceholder("Password").fill(randomPassword); + await page.getByPlaceholder(TEXTS.placeholderUsername).fill(randomName); + await page.getByPlaceholder(TEXTS.placeholderPassword).fill(randomPassword); await page.waitForSelector("text=Sign in", { timeout: 1500, }); - await page.getByRole("button", { name: "Sign In" }).click(); + await page.getByRole("button", { name: TEXTS.signIn }).click(); await page.evaluate(() => { sessionStorage.removeItem("testMockAutoLogin"); diff --git a/src/frontend/tests/utils/adjust-screen-view.ts b/src/frontend/tests/utils/adjust-screen-view.ts index 0fb8023690..73d484e830 100644 --- a/src/frontend/tests/utils/adjust-screen-view.ts +++ b/src/frontend/tests/utils/adjust-screen-view.ts @@ -27,13 +27,16 @@ export async function adjustScreenView( if (await zoomOutButton.isDisabled({ timeout: 1000 })) { break; - } else { - await zoomOutButton.click({ timeout: 1000 }); } + // `noWaitAfter` keeps the click from blocking on scheduled navigations. + // On a busy runner the zoom-out button can be ready while a background + // route is still in flight, and the default click would then sit there + // until the 1s timeout fires — turning a successful zoom into a flake. + await zoomOutButton.click({ timeout: 5000, noWaitAfter: true }); } if (fitViewButton > 0) { await page .getByTestId("canvas_controls_dropdown") - .click({ force: true, timeout: 1000 }); + .click({ force: true, timeout: 5000, noWaitAfter: true }); } } diff --git a/src/frontend/tests/utils/auth-helpers.ts b/src/frontend/tests/utils/auth-helpers.ts deleted file mode 100644 index af5b6f1433..0000000000 --- a/src/frontend/tests/utils/auth-helpers.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from "../fixtures"; - -export async function getAuthToken(request: any) { - const formData = new URLSearchParams(); - formData.append("username", "langflow"); - formData.append("password", "langflow"); - - const loginResponse = await request.post("/api/v1/login", { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - data: formData.toString(), - }); - - expect(loginResponse.status()).toBe(200); - const tokenData = await loginResponse.json(); - return tokenData.access_token; -} diff --git a/src/frontend/tests/utils/auth/mock-auto-login-disabled.ts b/src/frontend/tests/utils/auth/mock-auto-login-disabled.ts new file mode 100644 index 0000000000..76aa53e70e --- /dev/null +++ b/src/frontend/tests/utils/auth/mock-auto-login-disabled.ts @@ -0,0 +1,37 @@ +import type { Page } from "@playwright/test"; + +/** + * Stub the /api/v1/auto_login endpoint so the app falls back to manual + * sign-in, then perform the username/password login flow. + * + * Replaces the 3 inline copies of this route+init-script+login block. + * + * The caller is responsible for the subsequent assertions (e.g. waiting + * for the mainpage_title). + */ +export async function mockAutoLoginDisabled(page: Page): Promise { + await page.route("**/api/v1/auto_login", (route) => { + route.fulfill({ + status: 500, + contentType: "application/json", + body: JSON.stringify({ detail: { auto_login: false } }), + }); + }); + + await page.addInitScript(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).process = (window as any).process || {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const proc = (window as any).process as { env?: Record }; + const newEnv = { + ...(proc.env ?? {}), + LANGFLOW_AUTO_LOGIN: "false", + }; + Object.defineProperty(proc, "env", { + value: newEnv, + writable: true, + configurable: true, + }); + sessionStorage.setItem("testMockAutoLogin", "true"); + }); +} diff --git a/src/frontend/tests/utils/auth/mock-config.ts b/src/frontend/tests/utils/auth/mock-config.ts new file mode 100644 index 0000000000..c80cc64e11 --- /dev/null +++ b/src/frontend/tests/utils/auth/mock-config.ts @@ -0,0 +1,30 @@ +import type { Page } from "@playwright/test"; + +/** + * Override fields returned by /api/v1/config. The mock is "merge-shaped": + * everything in `overrides` replaces the matching field in the response; + * the rest is whatever the route originally returned. + * + * Use with `page.allowFlowErrors()` if the mock returns an `error: true` + * shape — fixtures.ts treats those as flow execution errors and fails + * the test unless explicitly allowed. + */ +export async function mockConfig( + page: Page, + overrides: Record, +): Promise { + await page.route("**/api/v1/config", async (route) => { + let baseBody: Record = {}; + try { + const response = await route.fetch(); + baseBody = await response.json(); + } catch { + // First-load: backend may not be reachable yet — start from empty. + } + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ ...baseBody, ...overrides }), + }); + }); +} diff --git a/src/frontend/tests/utils/await-bootstrap-test.ts b/src/frontend/tests/utils/await-bootstrap-test.ts index 60955fbb44..af7c03e0bd 100644 --- a/src/frontend/tests/utils/await-bootstrap-test.ts +++ b/src/frontend/tests/utils/await-bootstrap-test.ts @@ -45,8 +45,27 @@ export const awaitBootstrapTest = async ( attempts++; try { await page.getByTestId("new-project-btn").click(); + // Clicking the header "New Flow" button now navigates to a fresh + // flow and surfaces the FlowBuilderWelcome overlay before the + // templates modal. Race the overlay against the modal so we don't + // time out waiting for the modal when the overlay shows first. + await Promise.race([ + page.waitForSelector('[data-testid="flow-builder-welcome-panel"]', { + timeout: 30000, + }), + page.waitForSelector('[data-testid="modal-title"]', { + timeout: 30000, + }), + ]); + if ( + (await page + .locator('[data-testid="flow-builder-welcome-panel"]') + .count()) > 0 + ) { + await page.getByTestId("flow-builder-welcome-browse-more").click(); + } await page.waitForSelector('[data-testid="modal-title"]', { - timeout: 5000, + timeout: 30000, }); modalCount = await page.getByTestId("modal-title")?.count(); } catch (error) { diff --git a/src/frontend/tests/utils/build-data-transfer.ts b/src/frontend/tests/utils/build-data-transfer.ts deleted file mode 100644 index 133a95e4c0..0000000000 --- a/src/frontend/tests/utils/build-data-transfer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Page } from "@playwright/test"; - -export const buildDataTransfer = async (page: Page, fileContent: string) => { - return await page.evaluateHandle( - ({ fileContent }) => { - const dt = new DataTransfer(); - const byteCharacters = atob(fileContent); - const byteNumbers = new Array(byteCharacters.length); - for (let i = 0; i < byteCharacters.length; i++) { - byteNumbers[i] = byteCharacters.charCodeAt(i); - } - const byteArray = new Uint8Array(byteNumbers); - const file = new File([byteArray], "chain.png", { type: "image/png" }); - dt.items.add(file); - return dt; - }, - { fileContent }, - ); -}; diff --git a/src/frontend/tests/utils/clean-old-folders.ts b/src/frontend/tests/utils/clean-old-folders.ts index c1f949c097..c0bb936752 100644 --- a/src/frontend/tests/utils/clean-old-folders.ts +++ b/src/frontend/tests/utils/clean-old-folders.ts @@ -1,23 +1,27 @@ import type { Page } from "@playwright/test"; import { convertTestName } from "./convert-test-name"; +import { TEXTS } from "../utils/constants/texts"; export const cleanOldFolders = async (page: Page) => { - let numberOfFolders = await page.getByText("New Project").count(); + let numberOfFolders = await page.getByText(TEXTS.labelNewProject).count(); while (numberOfFolders > 0) { const getFirstFolderName = convertTestName( - (await page.getByText("New Project").first().textContent()) as string, + (await page + .getByText(TEXTS.labelNewProject) + .first() + .textContent()) as string, ); - await page.getByText("New Project").first().hover(); + await page.getByText(TEXTS.labelNewProject).first().hover(); const moreOptionsBtn = page .getByTestId(`more-options-button_${getFirstFolderName}`) .last(); await moreOptionsBtn.waitFor({ state: "visible", timeout: 5000 }); await moreOptionsBtn.click(); - await page.getByText("Delete", { exact: true }).last().click(); - await page.getByText("Delete", { exact: true }).last().click(); + await page.getByText(TEXTS.delete, { exact: true }).last().click(); + await page.getByText(TEXTS.delete, { exact: true }).last().click(); await page.waitForTimeout(500); numberOfFolders--; diff --git a/src/frontend/tests/utils/constants/selectors.ts b/src/frontend/tests/utils/constants/selectors.ts new file mode 100644 index 0000000000..6ec13aa395 --- /dev/null +++ b/src/frontend/tests/utils/constants/selectors.ts @@ -0,0 +1,7 @@ +/** + * Re-export of selector constants so callers can import from a stable path + * even if `testIds.ts` is renamed or split later. + */ +export { SELECTORS, TID } from "./testIds"; +export { TEXTS } from "./texts"; +export { ANIMATIONS, TIMEOUTS } from "./timeouts"; diff --git a/src/frontend/tests/utils/constants/testIds.ts b/src/frontend/tests/utils/constants/testIds.ts new file mode 100644 index 0000000000..ffd7646233 --- /dev/null +++ b/src/frontend/tests/utils/constants/testIds.ts @@ -0,0 +1,68 @@ +/** + * Centralised data-testid strings referenced from multiple spec files. + * + * Specs that touch only one of these test-ids should still use the + * constant — a single rename in the UI then needs only one change here. + */ +export const TID = { + // App shell + mainpageTitle: "mainpage_title", + newProjectBtn: "new-project-btn", + newProjectBtnEmptyPage: "new_project_btn_empty_page", + modalTitle: "modal-title", + userProfileSettings: "user-profile-settings", + + // Flow editor + blankFlow: "blank-flow", + sidebarSearchInput: "sidebar-search-input", + sidebarCustomComponentButton: "sidebar-custom-component-button", + sideNavAllTemplates: "side_nav_options_all-templates", + canvasControlsDropdown: "canvas_controls_dropdown", + fitView: "fit_view", + zoomOut: "zoom_out", + divGenericNode: "div-generic-node", + + // Build / run + buttonRunChatOutput: "button_run_chat output", + buttonStop: "button-stop", + playgroundBtnFlowIo: "playground-btn-flow-io", + + // Playground chat + inputChatPlayground: "input-chat-playground", + buttonSend: "button-send", + chatMessage: "div-chat-message", + sessionSelector: "session-selector", + newChat: "new-chat", + iconCoins: "icon-Coins", + + // Publish / shareable playground + publishButton: "publish-button", + publishSwitch: "publish-switch", + shareablePlayground: "shareable-playground", + apiAccessItem: "api-access-item", + apiTabCurl: "api_tab_curl", + + // Settings + settingsMenuHeader: "settings_menu_header", + iconChevronLeft: "icon-ChevronLeft", + + // Model providers + modelModel: "model_model", + manageModelProviders: "manage-model-providers", + popoverAnchorInputApiKey: "popover-anchor-input-api_key", + + // Edit / inspect + editFieldsButton: "edit-fields-button", + codeButtonModal: "code-button-modal", +} as const; + +/** + * Selector strings for elements without stable test-ids. Prefer adding a + * test-id to the UI rather than expanding this list. + */ +export const SELECTORS = { + reactFlowCanvasXPath: '//*[@id="react-flow-id"]', + sessionMoreMenuPattern: + '[data-testid^="session-"][data-testid$="-more-menu"]', + providerItemPattern: '[data-testid^="provider-item-"]', +} as const; diff --git a/src/frontend/tests/utils/constants/texts.ts b/src/frontend/tests/utils/constants/texts.ts new file mode 100644 index 0000000000..51edd83a67 --- /dev/null +++ b/src/frontend/tests/utils/constants/texts.ts @@ -0,0 +1,93 @@ +/** + * Centralised UI text strings referenced from more than one spec file. + * + * Every string here was found in ≥ 5 spec files by the analysis script + * in `CZL/E2E_TESTS_REFACTOR_PROPOSAL.md`. Specs that touch only one of + * these strings should still use the constant — a single rename in the + * product UI then needs only one change here. + * + * Scope: text the production UI renders (labels, placeholders, toast + * messages, role names) and the fill values that have to match it. Do + * NOT add per-test fixture strings ("my first session", "Say hi", + * "edit_bot_1") — those are scenario data, not UI invariants. + */ +export const TEXTS = { + // ─── Action buttons / menu items ──────────────────────────────────── + /** Generic "Close" button — modals, popovers, fullscreen exits. */ + close: "Close", + /** Generic "Delete" button — confirmation modals, sidebar menus. */ + delete: "Delete", + /** Generic "Save" button — settings, profile, etc. */ + save: "Save", + /** "Check & Save" submit on custom-component code editor. */ + checkAndSave: "Check & Save", + /** "Edit Prompt" trigger on Prompt template node. */ + editPrompt: "Edit Prompt", + /** Sign-in form submit button (role=button, name="Sign In"). */ + signIn: "Sign In", + /** Build-cancel button (role=button, name="Stop"). */ + stop: "Stop", + /** App header / user menu items. */ + settings: "Settings", + logout: "Logout", + exit: "Exit", + /** Right-hand panel toggle (role=button, name="Playground"). */ + playground: "Playground", + + // ─── Starter projects / templates (heading or card names) ─────────── + templateBasicPrompting: "Basic Prompting", + templateSimpleAgent: "Simple Agent", + templateBasicRag: "Basic RAG", + + // ─── Component display names (sidebar & canvas labels) ────────────── + componentChatInput: "Chat Input", + componentLanguageModel: "Language Model", + componentOutput: "Component Output", + + // ─── Generic UI labels rendered in the projects/main page ─────────── + labelNewProject: "New Project", + labelMyFiles: "My Files", + labelComponents: "Components", + /** Sub-section of the agent panel — rendered both as plain text and as + * the `text=toolset` waitForSelector target. */ + labelToolset: "toolset", + labelNoInputMessage: "No input message provided.", + labelHelloFromAi: "Hello from AI", + + // ─── Toasts / status messages ─────────────────────────────────────── + toastProjectDeleted: "Project deleted successfully", + toastApiKeySaved: "Success! Your API Key has been saved.", + /** Sub-string emitted by the build-flow log when a build finishes + * successfully — used as `text=built successfully` in 36 specs. */ + toastBuiltSuccessfully: "built successfully", + + // ─── Auth / login screen ──────────────────────────────────────────── + /** Visible on the sign-in route when LANGFLOW_AUTO_LOGIN=false. */ + authSignInHeader: "sign in to langflow", + /** Default seeded username/password ("langflow"/"langflow"). */ + authDefaultCredential: "langflow", + + // ─── Form placeholders (getByPlaceholder) ─────────────────────────── + placeholderUsername: "Username", + placeholderPassword: "Password", + placeholderMessage: "Message", + placeholderEmpty: "Empty", + placeholderApiKey: "Insert your API Key", + placeholderSendMessage: "Send a message...", + placeholderVariableName: "Enter a name for the variable...", + + // ─── Sidebar search queries (.fill(...) on the components search) ─── + /** NOTE: these are the LOWERCASE strings typed into the sidebar + * search input — separate from the display-name constants above + * (`componentChatInput`, etc.) which are PascalCase / Title Case. */ + searchChatInput: "chat input", + searchChatOutput: "chat output", + searchTextInput: "text input", + searchTextOutput: "text output", + searchPrompt: "prompt", + searchUrl: "url", + /** Provider names — uppercase as rendered in the model-provider modal. */ + providerOpenAi: "OpenAI", + /** Same provider as a lowercase search query. */ + providerOpenAiSearch: "openai", +} as const; diff --git a/src/frontend/tests/utils/constants/timeouts.ts b/src/frontend/tests/utils/constants/timeouts.ts new file mode 100644 index 0000000000..b12679764a --- /dev/null +++ b/src/frontend/tests/utils/constants/timeouts.ts @@ -0,0 +1,36 @@ +/** + * Single source of truth for Playwright timeouts. + * + * Every hardcoded `timeout: ` in a spec should map to one of these. + * Animation waits (`waitForTimeout(X)` with a documented reason) live in + * `ANIMATIONS` so the *why* is named at the call site. + */ +export const TIMEOUTS = { + /** Short interactions (sidebar typeahead, drag prep). */ + fast: 1_000, + /** Single-element appears after a click. */ + short: 3_000, + /** Modal open, listbox populated, navigation settle. */ + medium: 10_000, + /** Page load, settings header, common assertions. */ + standard: 30_000, + /** Component mounts after a starter project loads. */ + componentMount: 100_000, + /** Backend operations that may queue (auto-login off, slow Windows CI). */ + long: 60_000, + /** Build finished / stop-button hidden / large flow runs. */ + buildComplete: 120_000, +} as const; + +/** + * Documented animation/propagation waits. Use these instead of bare + * `page.waitForTimeout(N)` — the name explains *why* the sleep is needed. + */ +export const ANIMATIONS = { + /** Wait for the fullscreen playground overlay animation to settle. */ + fullscreenPlayground: 1_000, + /** Wait for the publish-switch toggle to propagate to the backend. */ + publishTogglePropagation: 2_000, + /** Wait for the new shareable-playground tab to finish mounting. */ + shareablePlaygroundMount: 3_000, +} as const; diff --git a/src/frontend/tests/utils/env/load-dotenv.ts b/src/frontend/tests/utils/env/load-dotenv.ts new file mode 100644 index 0000000000..3600e68d2e --- /dev/null +++ b/src/frontend/tests/utils/env/load-dotenv.ts @@ -0,0 +1,18 @@ +import * as dotenv from "dotenv"; +import path from "path"; + +/** + * Load the local .env when not running in CI. Idempotent. + * + * Specs that need env vars (OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.) used + * to copy-paste a 3-line `if (!process.env.CI) { dotenv.config(...) }` + * block. Call this helper instead — once at module load, or via the + * Playwright globalSetup once that wiring lands. + * + * @param specDir Pass `__dirname` from the calling spec so the relative + * path to `tests/.env` resolves correctly. + */ +export function loadDotenvIfLocal(specDir: string): void { + if (process.env.CI) return; + dotenv.config({ path: path.resolve(specDir, "../../.env") }); +} diff --git a/src/frontend/tests/utils/env/skip-if-missing.ts b/src/frontend/tests/utils/env/skip-if-missing.ts new file mode 100644 index 0000000000..fdfa091319 --- /dev/null +++ b/src/frontend/tests/utils/env/skip-if-missing.ts @@ -0,0 +1,49 @@ +import { test } from "../../fixtures"; + +/** + * Named skip-guards for environment-gated tests. + * + * Use inside a `test(...)` body (NOT at module scope — Playwright's + * `test.skip(condition, reason)` must be called from inside a test). + * + * test("foo", async ({ page }) => { + * skipIfMissing.openAiKey(); + * ... + * }); + */ +export const skipIfMissing = { + openAiKey: (): void => { + test.skip( + !process?.env?.OPENAI_API_KEY, + "OPENAI_API_KEY required to run this test", + ); + }, + + anthropicKey: (): void => { + test.skip( + !process?.env?.ANTHROPIC_API_KEY, + "ANTHROPIC_API_KEY required to run this test", + ); + }, + + storeApiKey: (): void => { + test.skip( + !process?.env?.STORE_API_KEY, + "STORE_API_KEY required to run this test", + ); + }, + + autoLoginDisabled: (): void => { + test.skip( + process?.env?.LANGFLOW_AUTO_LOGIN !== "false", + "Server must run with AUTO_LOGIN=FALSE for this test", + ); + }, + + wxoDeploymentsEnabled: (): void => { + test.skip( + process?.env?.LANGFLOW_FEATURE_WXO_DEPLOYMENTS !== "true", + "Requires LANGFLOW_FEATURE_WXO_DEPLOYMENTS=true", + ); + }, +}; diff --git a/src/frontend/tests/utils/evaluate-input-react-state-changes.ts b/src/frontend/tests/utils/evaluate-input-react-state-changes.ts deleted file mode 100644 index 97f2fcaf2f..0000000000 --- a/src/frontend/tests/utils/evaluate-input-react-state-changes.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Page } from "@playwright/test"; - -export const evaluateReactStateChanges = async ( - page: Page, - selector: string, - value: string, -) => { - await page.evaluate( - ({ selector, value }) => { - const inputElement = document.querySelector(selector) as HTMLInputElement; - if (inputElement) { - const prototype = Object.getPrototypeOf(inputElement); - const nativeInputValueSetter = Object.getOwnPropertyDescriptor( - prototype, - "value", - )?.set; - if (nativeInputValueSetter) { - nativeInputValueSetter.call(inputElement, value); - inputElement.dispatchEvent(new Event("input", { bubbles: true })); - } - } - }, - { selector, value }, - ); -}; diff --git a/src/frontend/tests/utils/flow/add-component-from-sidebar.ts b/src/frontend/tests/utils/flow/add-component-from-sidebar.ts new file mode 100644 index 0000000000..169aa01c40 --- /dev/null +++ b/src/frontend/tests/utils/flow/add-component-from-sidebar.ts @@ -0,0 +1,81 @@ +import type { Page } from "@playwright/test"; +import { SELECTORS, TID } from "../constants/testIds"; +import { TIMEOUTS } from "../constants/timeouts"; + +export type AddComponentOpts = { + /** Search query typed into the sidebar input. */ + search: string; + /** Exact `data-testid` of the component row in the sidebar (e.g. `input_outputChat Output`). */ + testId: string; + /** If provided, the component is dragged to this position on the canvas. */ + position?: { x: number; y: number }; + /** If provided, hover + click the inline add button instead of dragging. */ + hoverAdd?: boolean; + /** + * Display name slug for the inline "+" button, when the prefix-stripped + * row testId doesn't already match it. Example: pass `"chat-output"` to + * target `add-component-button-chat-output`. Defaults to the slug of the + * row testId with the leading `_` prefix removed + * (matches `convertTestName(display_name)` from the production UI). + */ + addButtonSlug?: string; +}; + +/** + * Convert the sidebar row testId (e.g. `input_outputChat Output`) into the + * slug used by the inline add button (`chat-output`). Mirrors the + * production `convertTestName(display_name)` helper, but reverse-engineered + * from the row testId because that is what tests already pass in. + */ +function rowTestIdToAddButtonSlug(testId: string): string { + // Sidebar rows are emitted as `${category}${display_name}` (no + // separator). The leading category is always lowercase + may contain + // underscores; the display name starts with the first uppercase letter + // or digit. Splitting at that boundary gives us the human-readable + // display name, which we then run through the same slug rule as the + // production UI (`convertTestName`). + const match = testId.match(/^([a-z_]+)([A-Z0-9].*)$/); + const displayName = match ? match[2] : testId; + return displayName.replace(/ /g, "-").toLowerCase(); +} + +/** + * Search the sidebar and add a component to the canvas. + * + * Replaces the 5-line ritual that appears 60+ times across the suite: + * await page.getByTestId("sidebar-search-input").click(); + * await page.getByTestId("sidebar-search-input").fill(""); + * await page.waitForSelector('[data-testid=""]', { timeout: 100000 }); + * await page.getByTestId("").dragTo(, { ... }); + * + * Pass `position` to drag; pass `hoverAdd` to use the inline + button. + */ +export async function addComponentFromSidebar( + page: Page, + { search, testId, position, hoverAdd, addButtonSlug }: AddComponentOpts, +): Promise { + await page.getByTestId(TID.sidebarSearchInput).click(); + await page.getByTestId(TID.sidebarSearchInput).fill(search); + await page.waitForSelector(`[data-testid="${testId}"]`, { + timeout: TIMEOUTS.componentMount, + }); + + if (hoverAdd) { + const slug = addButtonSlug ?? rowTestIdToAddButtonSlug(testId); + // Scope the "+" button query to the targeted row — the sidebar can + // surface the same `add-component-button-` testid on multiple + // rows (e.g. `input_outputChat Input` AND `saved_componentsChat Input` + // both render an `add-component-button-chat-input`), and a top-level + // `page.getByTestId(...)` then trips Playwright's strict-mode check. + const row = page.getByTestId(testId); + await row.hover(); + await row.getByTestId(`add-component-button-${slug}`).click(); + return; + } + + await page + .getByTestId(testId) + .dragTo(page.locator(SELECTORS.reactFlowCanvasXPath), { + targetPosition: position ?? { x: 200, y: 200 }, + }); +} diff --git a/src/frontend/tests/utils/flow/build-flow-and-wait.ts b/src/frontend/tests/utils/flow/build-flow-and-wait.ts new file mode 100644 index 0000000000..da8740cb4c --- /dev/null +++ b/src/frontend/tests/utils/flow/build-flow-and-wait.ts @@ -0,0 +1,19 @@ +import type { Page } from "@playwright/test"; +import { TID } from "../constants/testIds"; +import { TIMEOUTS } from "../constants/timeouts"; + +import { TEXTS } from "../../utils/constants/texts"; +/** + * Click the Run button on the Chat Output and wait for "built successfully". + * + * Replaces the 2-line build ritual that appears in 17+ specs. + */ +export async function buildFlowAndWait( + page: Page, + options?: { timeoutMs?: number }, +): Promise { + await page.getByTestId(TID.buttonRunChatOutput).click(); + await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { + timeout: options?.timeoutMs ?? TIMEOUTS.buildComplete, + }); +} diff --git a/src/frontend/tests/utils/flow/go-back-to-projects.ts b/src/frontend/tests/utils/flow/go-back-to-projects.ts new file mode 100644 index 0000000000..6fd2ac47d2 --- /dev/null +++ b/src/frontend/tests/utils/flow/go-back-to-projects.ts @@ -0,0 +1,22 @@ +import type { Page } from "@playwright/test"; +import { TID } from "../constants/testIds"; + +/** + * Click the back chevron to return from a flow editor to the projects page. + * + * Both `.first()` and `.last()` are seen in existing specs because some + * pages render extra chevrons in the header. The default uses `.first()` + * (matches the majority of call sites); pass `last: true` for the + * legacy MCP / auto-save patterns. + */ +export async function goBackToProjects( + page: Page, + options?: { last?: boolean }, +): Promise { + const chevron = page.getByTestId(TID.iconChevronLeft); + if (options?.last) { + await chevron.last().click(); + } else { + await chevron.first().click(); + } +} diff --git a/src/frontend/tests/utils/flow/open-blank-flow.ts b/src/frontend/tests/utils/flow/open-blank-flow.ts new file mode 100644 index 0000000000..606ca22ee0 --- /dev/null +++ b/src/frontend/tests/utils/flow/open-blank-flow.ts @@ -0,0 +1,20 @@ +import type { Page } from "@playwright/test"; +import { awaitBootstrapTest } from "../await-bootstrap-test"; +import { TID } from "../constants/testIds"; +import { TIMEOUTS } from "../constants/timeouts"; + +/** + * Bootstrap the app and open a blank flow. + * + * Replaces the 3-line ritual that appears in 50+ spec files: + * await awaitBootstrapTest(page); + * await page.waitForSelector('[data-testid="blank-flow"]', { timeout: 30000 }); + * await page.getByTestId("blank-flow").click(); + */ +export async function openBlankFlow(page: Page): Promise { + await awaitBootstrapTest(page); + await page.waitForSelector(`[data-testid="${TID.blankFlow}"]`, { + timeout: TIMEOUTS.standard, + }); + await page.getByTestId(TID.blankFlow).click(); +} diff --git a/src/frontend/tests/utils/flow/open-starter-project.ts b/src/frontend/tests/utils/flow/open-starter-project.ts new file mode 100644 index 0000000000..a818675aa0 --- /dev/null +++ b/src/frontend/tests/utils/flow/open-starter-project.ts @@ -0,0 +1,29 @@ +import type { Page } from "@playwright/test"; +import { awaitBootstrapTest } from "../await-bootstrap-test"; +import { TID } from "../constants/testIds"; + +/** + * Bootstrap the app, open the templates panel, and click the heading + * for a starter project. + * + * Replaces the 3-step ritual that appears 50+ times across `core/integrations/`: + * await awaitBootstrapTest(page); + * await page.getByTestId("side_nav_options_all-templates").click(); + * await page.getByRole("heading", { name: "