From f141e0715191da8d5893c0a7d0d97347b51993bf Mon Sep 17 00:00:00 2001 From: cristhianzl Date: Thu, 21 May 2026 16:00:38 -0300 Subject: [PATCH 1/9] refactor playwright tests: remove duplicate tests and add good practices --- .../features/actionsMainPage-shard-1.spec.ts | 10 +- .../core/features/deployment-create.spec.ts | 61 +--- .../core/features/deployment-edit.spec.ts | 41 +-- .../features/deployment-providers.spec.ts | 36 +-- .../features/deployment-test-modal.spec.ts | 36 +-- .../core/features/deployments-page.spec.ts | 56 +--- .../tests/core/features/folders.spec.ts | 13 +- .../tests/core/features/group.spec.ts | 41 --- .../core/features/modelProviderCount.spec.ts | 184 ----------- .../core/features/modelProviderModal.spec.ts | 31 ++ .../tests/core/features/playground.spec.ts | 108 +++---- .../tests/core/features/publish-flow.spec.ts | 93 ++---- .../tests/core/features/run-flow.spec.ts | 2 +- .../core/features/saveComponents.spec.ts | 115 ------- .../shareable-playground-auth.spec.ts | 153 +++------ .../shareable-playground-persistence.spec.ts | 293 ++++++------------ ...shareable-playground-token-display.spec.ts | 106 ++----- .../tests/core/features/store-shard-2.spec.ts | 37 +-- .../tests/core/features/toolModeGroup.spec.ts | 32 -- .../core/integrations/Basic Prompting.spec.ts | 52 ++-- .../core/integrations/Blog Writer.spec.ts | 30 +- .../Custom Component Generator.spec.ts | 9 +- .../core/integrations/Document QA.spec.ts | 33 +- .../core/integrations/Dynamic Agent.spec.ts | 56 ---- .../Financial Report Parser.spec.ts | 74 ----- .../integrations/Hierarchical Agent.spec.ts | 53 ---- .../Image Sentiment Analysis.spec.ts | 91 ------ .../integrations/Instagram Copywriter.spec.ts | 23 +- .../integrations/Invoice Summarizer.spec.ts | 16 +- .../core/integrations/Market Research.spec.ts | 22 +- .../core/integrations/Memory Chatbot.spec.ts | 27 +- .../core/integrations/News Aggregator.spec.ts | 23 +- .../core/integrations/Pokedex Agent.spec.ts | 24 +- .../Portfolio Website Code Generator.spec.ts | 79 ----- .../integrations/Price Deal Finder.spec.ts | 111 ------- .../core/integrations/Prompt Chaining.spec.ts | 22 +- .../Research Translation Loop.spec.ts | 15 +- .../SEO Keyword Generator.spec.ts | 27 +- .../core/integrations/SaaS Pricing.spec.ts | 27 +- .../Sequential Task Agent.spec.ts | 42 --- .../integrations/Simple Agent Memory.spec.ts | 15 +- .../core/integrations/Simple Agent.spec.ts | 23 +- .../integrations/Social Media Agent.spec.ts | 16 +- .../Text Sentiment Analysis.spec.ts | 15 +- .../Travel Planning Agent.spec.ts | 16 +- .../Twitter Thread Generator.spec.ts | 20 +- .../integrations/Youtube Analysis.spec.ts | 23 +- .../assistant-panel-integration.spec.ts | 22 +- .../core/integrations/assistant-panel.spec.ts | 14 +- .../core/integrations/textInputOutput.spec.ts | 174 ----------- .../regression/generalBugs-prompt.spec.ts | 14 +- .../regression/generalBugs-shard-5.spec.ts | 3 +- .../regression/generalBugs-shard-9.spec.ts | 14 +- .../regression/ime-input-composition.spec.ts | 9 +- .../session-deletion-data-leakage.spec.ts | 13 +- .../unit/agentDefaultToolsComponent.spec.ts | 6 +- .../tests/core/unit/chatInputOutput.spec.ts | 10 +- .../core/unit/codeAreaModalComponent.spec.ts | 10 +- .../tests/core/unit/dropdownComponent.spec.ts | 11 +- .../core/unit/fileUploadComponent.spec.ts | 17 +- .../tests/core/unit/floatComponent.spec.ts | 9 +- .../tests/core/unit/inputComponent.spec.ts | 9 +- .../tests/core/unit/intComponent.spec.ts | 9 +- .../core/unit/keyPairListComponent.spec.ts | 11 +- .../tests/core/unit/linkComponent.spec.ts | 22 +- .../tests/core/unit/nestedComponent.spec.ts | 9 +- .../core/unit/promptModalComponent.spec.ts | 40 +-- .../core/unit/queryInputComponent.spec.ts | 21 +- .../tests/core/unit/sliderComponent.spec.ts | 39 +-- .../tests/core/unit/tabComponent.spec.ts | 22 +- .../core/unit/tableInputComponent.spec.ts | 193 ------------ .../core/unit/textAreaModalComponent.spec.ts | 9 +- .../tests/core/unit/toggleComponent.spec.ts | 13 +- .../tests/core/unit/webhookComponent.spec.ts | 18 +- .../features/actionsMainPage-shard-0.spec.ts | 8 +- .../extended/features/auto-save-off.spec.ts | 11 +- .../extended/features/bulk-actions.spec.ts | 6 +- .../features/copy-button-in-output.spec.ts | 6 +- .../features/curlApiGeneration.spec.ts | 22 -- .../features/deleteComponents.spec.ts | 21 +- .../extended/features/deleteFlows.spec.ts | 24 +- .../edit-name-description-node.spec.ts | 18 +- .../extended/features/edit-tools.spec.ts | 6 +- .../extended/features/files-page.spec.ts | 6 +- .../tests/extended/features/flowPage.spec.ts | 22 -- .../extended/features/flowSettings.spec.ts | 12 +- .../features/integration-side-bar.spec.ts | 6 +- .../features/langflowShortcuts.spec.ts | 9 +- .../features/limit-file-size-upload.spec.ts | 15 +- .../tests/extended/features/lock-flow.spec.ts | 15 +- .../extended/features/loop-component.spec.ts | 5 +- .../extended/features/mcp-server.spec.ts | 45 +-- .../tests/extended/features/minimize.spec.ts | 9 +- .../extended/features/notifications.spec.ts | 5 +- .../features/outdated-actions.spec.ts | 4 +- .../features/output-modal-copy-button.spec.ts | 10 +- .../features/refresh-dropdown-list.spec.ts | 9 +- .../features/sticky-notes-dimensions.spec.ts | 37 +-- .../extended/features/sticky-notes.spec.ts | 10 +- .../features/stop-button-playground.spec.ts | 121 +++----- .../extended/features/store-shard-0.spec.ts | 6 +- .../extended/features/store-shard-1.spec.ts | 55 ++-- .../extended/features/store-shard-3.spec.ts | 30 +- .../tests/extended/features/tool-mode.spec.ts | 5 +- .../tests/extended/features/twoEdges.spec.ts | 15 +- .../validate-raise-errors-components.spec.ts | 7 +- .../chatInputOutputUser-shard-1.spec.ts | 15 +- .../chatInputOutputUser-shard-2.spec.ts | 15 +- .../integrations/youtube-transcripts.spec.ts | 45 --- ...l-bugs-agent-anthropic-integration.spec.ts | 44 +-- ...neral-bugs-agent-images-playground.spec.ts | 54 ++-- ...t-sum-duplicate-message-playground.spec.ts | 14 +- ...-component-webhook-api-key-display.spec.ts | 9 +- ...ontend-crashing-on-invalid-replace.spec.ts | 11 +- .../general-bugs-hidden-input-edges.spec.ts | 2 - .../general-bugs-reset-flow-run.spec.ts | 9 +- .../general-bugs-shard-3836.spec.ts | 15 +- .../general-bugs-shard-3909.spec.ts | 15 +- .../general-bugs-truncate-results.spec.ts | 67 ---- .../regression/generalBugs-shard-1.spec.ts | 17 +- .../regression/generalBugs-shard-10.spec.ts | 15 +- .../regression/generalBugs-shard-11.spec.ts | 8 +- .../regression/generalBugs-shard-13.spec.ts | 15 +- .../regression/generalBugs-shard-3.spec.ts | 32 +- .../regression/generalBugs-shard-6.spec.ts | 10 +- .../regression/generalBugs-shard-7.spec.ts | 10 +- src/frontend/tests/fixtures.ts | 10 +- .../templates/unit-test-components.spec.ts | 3 - src/frontend/tests/utils/add-new-api-keys.ts | 23 -- src/frontend/tests/utils/auth-helpers.ts | 18 -- .../utils/auth/mock-auto-login-disabled.ts | 37 +++ src/frontend/tests/utils/auth/mock-config.ts | 30 ++ .../tests/utils/build-data-transfer.ts | 19 -- .../tests/utils/constants/selectors.ts | 6 + src/frontend/tests/utils/constants/testIds.ts | 67 ++++ .../tests/utils/constants/timeouts.ts | 36 +++ .../evaluate-input-react-state-changes.ts | 25 -- .../utils/flow/add-component-from-sidebar.ts | 50 +++ .../tests/utils/flow/build-flow-and-wait.ts | 18 ++ .../tests/utils/flow/go-back-to-projects.ts | 22 ++ .../tests/utils/flow/open-blank-flow.ts | 20 ++ .../tests/utils/flow/open-starter-project.ts | 29 ++ .../utils/flow/replace-component-code.ts | 25 ++ .../playground/publish-and-open-shareable.ts | 49 +++ .../tests/utils/playground/sessions.ts | 21 ++ src/frontend/tests/utils/run-chat-output.ts | 14 - src/frontend/tests/utils/types.ts | 11 + 147 files changed, 1203 insertions(+), 3505 deletions(-) delete mode 100644 src/frontend/tests/core/features/group.spec.ts delete mode 100644 src/frontend/tests/core/features/modelProviderCount.spec.ts delete mode 100644 src/frontend/tests/core/features/saveComponents.spec.ts delete mode 100644 src/frontend/tests/core/features/toolModeGroup.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Dynamic Agent.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Financial Report Parser.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Hierarchical Agent.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Image Sentiment Analysis.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Portfolio Website Code Generator.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Price Deal Finder.spec.ts delete mode 100644 src/frontend/tests/core/integrations/Sequential Task Agent.spec.ts delete mode 100644 src/frontend/tests/core/integrations/textInputOutput.spec.ts delete mode 100644 src/frontend/tests/core/unit/tableInputComponent.spec.ts delete mode 100644 src/frontend/tests/extended/features/curlApiGeneration.spec.ts delete mode 100644 src/frontend/tests/extended/features/flowPage.spec.ts delete mode 100644 src/frontend/tests/extended/integrations/youtube-transcripts.spec.ts delete mode 100644 src/frontend/tests/extended/regression/general-bugs-truncate-results.spec.ts delete mode 100644 src/frontend/tests/templates/unit-test-components.spec.ts delete mode 100644 src/frontend/tests/utils/add-new-api-keys.ts delete mode 100644 src/frontend/tests/utils/auth-helpers.ts create mode 100644 src/frontend/tests/utils/auth/mock-auto-login-disabled.ts create mode 100644 src/frontend/tests/utils/auth/mock-config.ts delete mode 100644 src/frontend/tests/utils/build-data-transfer.ts create mode 100644 src/frontend/tests/utils/constants/selectors.ts create mode 100644 src/frontend/tests/utils/constants/testIds.ts create mode 100644 src/frontend/tests/utils/constants/timeouts.ts delete mode 100644 src/frontend/tests/utils/evaluate-input-react-state-changes.ts create mode 100644 src/frontend/tests/utils/flow/add-component-from-sidebar.ts create mode 100644 src/frontend/tests/utils/flow/build-flow-and-wait.ts create mode 100644 src/frontend/tests/utils/flow/go-back-to-projects.ts create mode 100644 src/frontend/tests/utils/flow/open-blank-flow.ts create mode 100644 src/frontend/tests/utils/flow/open-starter-project.ts create mode 100644 src/frontend/tests/utils/flow/replace-component-code.ts create mode 100644 src/frontend/tests/utils/playground/publish-and-open-shareable.ts create mode 100644 src/frontend/tests/utils/playground/sessions.ts delete mode 100644 src/frontend/tests/utils/run-chat-output.ts create mode 100644 src/frontend/tests/utils/types.ts 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 9215390ef98e..a09e63ae55de 100644 --- a/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts +++ b/src/frontend/tests/core/features/actionsMainPage-shard-1.spec.ts @@ -1,4 +1,4 @@ -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { adjustScreenView } from "../../utils/adjust-screen-view"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; @@ -34,7 +34,7 @@ test( //confirm the deletion in the modal await page.getByText("Delete").last().click(); - await page.getByText("Selected items deleted successfully").isVisible(); + await expect(page.getByText("Selected items deleted successfully")).toBeVisible(); }, ); @@ -50,7 +50,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,7 +70,7 @@ 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(); }); @@ -131,7 +131,7 @@ test( 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("Chat Input", { 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/deployment-create.spec.ts b/src/frontend/tests/core/features/deployment-create.spec.ts index 4367f386dcb2..b33d9e34c5cd 100644 --- a/src/frontend/tests/core/features/deployment-create.spec.ts +++ b/src/frontend/tests/core/features/deployment-create.spec.ts @@ -12,6 +12,12 @@ 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 +231,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 +251,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 +273,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 +309,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 +353,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 +380,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 +430,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 +464,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 +486,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 +508,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 +546,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 ba8062ccaef6..7d126ec2cfb0 100644 --- a/src/frontend/tests/core/features/deployment-edit.spec.ts +++ b/src/frontend/tests/core/features/deployment-edit.spec.ts @@ -12,6 +12,12 @@ 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 +107,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 +129,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 +157,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 +179,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 +219,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 +363,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 +459,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 44d603797580..8a52a72a9107 100644 --- a/src/frontend/tests/core/features/deployment-providers.spec.ts +++ b/src/frontend/tests/core/features/deployment-providers.spec.ts @@ -6,6 +6,12 @@ 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 +25,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 +51,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 +80,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 +118,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 +182,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 +237,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 8a36ca771216..91297e7e6d8e 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"; + +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,11 +50,6 @@ 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); @@ -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; @@ -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); @@ -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); @@ -227,11 +213,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> = []; @@ -303,11 +284,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); diff --git a/src/frontend/tests/core/features/deployments-page.spec.ts b/src/frontend/tests/core/features/deployments-page.spec.ts index d06d0956d758..53ca72c4b2a7 100644 --- a/src/frontend/tests/core/features/deployments-page.spec.ts +++ b/src/frontend/tests/core/features/deployments-page.spec.ts @@ -6,6 +6,12 @@ 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 +24,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 +53,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 +81,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 +111,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 +139,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 +169,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 +201,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 +238,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 +286,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 +340,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/folders.spec.ts b/src/frontend/tests/core/features/folders.spec.ts index ef4d1d173ef0..7849ac8efb15 100644 --- a/src/frontend/tests/core/features/folders.spec.ts +++ b/src/frontend/tests/core/features/folders.spec.ts @@ -18,16 +18,15 @@ test( }); await page.getByTestId("icon-ChevronLeft").first().click(); - await page.getByPlaceholder("Search flows").first().isVisible(); - await page.getByText("Flows").first().isVisible(); + await expect(page.getByPlaceholder("Search flows").first()).toBeVisible(); + await expect(page.getByText("Flows").first()).toBeVisible(); if (await page.getByText("Components").first().isVisible()) { - await page.getByText("Components").first().isVisible(); + await expect(page.getByText("Components").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 expect(page.getByText("All").first()).toBeVisible(); + await expect(page.getByText("Select All").first()).toBeVisible(); await page.getByTestId("add-project-button").click(); await page .locator("[data-testid='project-sidebar']") 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 98715c99d2fa..000000000000 --- 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/modelProviderCount.spec.ts b/src/frontend/tests/core/features/modelProviderCount.spec.ts deleted file mode 100644 index 921f34e96d28..000000000000 --- 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 eeebcbd8b4c8..4afdc5d6d631 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 ef7a225e1c69..f891ae7307b1 100644 --- a/src/frontend/tests/core/features/playground.spec.ts +++ b/src/frontend/tests/core/features/playground.spec.ts @@ -1,68 +1,44 @@ -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"; test( "fresh start playground", { tag: ["@release", "@workspace", "@api"] }, async ({ page }) => { - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - await awaitBootstrapTest(page); - - await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, - }); + loadDotenvIfLocal(__dirname); - 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 @@ -79,13 +55,13 @@ test( .click(); await page.getByRole("button", { name: "Playground", exact: true }).click(); - await page.waitForSelector('[data-testid="input-chat-playground"]', { - timeout: 100000, + 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 +95,51 @@ 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(); // 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"), + page.getByTestId(TID.sessionSelector).getByText("my first session"), ).toBeVisible(); // 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"), + page.getByTestId(TID.sessionSelector).getByText("my first session"), ).toBeVisible(); // 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 c20c79441940..fc5c41288520 100644 --- a/src/frontend/tests/core/features/publish-flow.spec.ts +++ b/src/frontend/tests/core/features/publish-flow.spec.ts @@ -1,106 +1,75 @@ 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"; 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 page.getByTestId("blank-flow").click(); - await page.waitForSelector('[data-testid="sidebar-search-input"]', { - timeout: 5000, + await openBlankFlow(page); + await page.waitForSelector(`[data-testid="${TID.sidebarSearchInput}"]`, { + timeout: TIMEOUTS.short, }); - 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 addComponentFromSidebar(page, { + search: "chat input", + testId: "input_outputChat Input", + hoverAdd: true, }); - 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.waitForTimeout(3000); + await page.getByTestId(TID.publishButton).click(); - 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("publish-switch")).toBeVisible({ - timeout: 10000, - }); - } catch (error) { - console.error("Error waiting for publish operation:", error); - throw error; - } - - await page.waitForTimeout(2000); + await expect(page.getByTestId(TID.publishSwitch)).toBeVisible({ + timeout: TIMEOUTS.medium, + }); - 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 }); + .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.getByTestId(TID.buttonSend).last().click(); const stopButton = newPage.getByRole("button", { name: "Stop" }); - await stopButton.waitFor({ state: "visible", timeout: 30000 }); + 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(); await page.goto(newUrl); - await page.waitForTimeout(2000); try { - await expect(page.getByTestId("mainpage_title")).toBeVisible({ - timeout: 10000, + await expect(page.getByTestId(TID.mainpageTitle)).toBeVisible({ + timeout: TIMEOUTS.medium, }); } catch (_error) { await page.reload(); - await expect(page.getByTestId("mainpage_title")).toBeVisible({ - timeout: 10000, + await expect(page.getByTestId(TID.mainpageTitle)).toBeVisible({ + timeout: TIMEOUTS.medium, }); } }, diff --git a/src/frontend/tests/core/features/run-flow.spec.ts b/src/frontend/tests/core/features/run-flow.spec.ts index be079aa32e25..799d0f0e4bf2 100644 --- a/src/frontend/tests/core/features/run-flow.spec.ts +++ b/src/frontend/tests/core/features/run-flow.spec.ts @@ -77,7 +77,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(); 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 7d64ad646aa7..000000000000 --- 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 019344e50c6e..e6b23564a744 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", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - - 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, - }); + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - // Wait for stop button to disappear (streaming finished) - await playgroundPage.waitForSelector('[data-testid="button-stop"]', { - state: "detached", - timeout: 120000, + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); + + await sendPlaygroundMessage(playgroundPage, "Tell me a short joke", { + surface: "shareable", }); - // 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", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - const playgroundPage = await setupShareablePlayground(page, context); + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(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 c07da4a3d62e..b95719589303 100644 --- a/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts +++ b/src/frontend/tests/core/features/shareable-playground-persistence.spec.ts @@ -1,44 +1,30 @@ -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. +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"; + /** - * Helper: mock auto-login as disabled and log in manually. + * Stub auto-login, navigate to the sign-in page, 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 } }), - }); - }); - - 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"); - }); +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=sign in to langflow", { + timeout: TIMEOUTS.standard, + }); await page.getByPlaceholder("Username").fill("langflow"); await page.getByPlaceholder("Password").fill("langflow"); @@ -49,113 +35,47 @@ async function setupAutoLoginOff(page: any) { await page.getByRole("button", { name: "Sign In" }).click(); - await page.waitForSelector('[data-testid="mainpage_title"]', { - timeout: 30000, - }); -} - -/** - * 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.waitForSelector(`[data-testid="${TID.mainpageTitle}"]`, { + timeout: TIMEOUTS.standard, }); - 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 +85,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 +118,49 @@ 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 +170,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 10c9443b55bd..cd704369fdab 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,38 @@ -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"; 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", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } + skipIfMissing.openAiKey(); + loadDotenvIfLocal(__dirname); - await awaitBootstrapTest(page); + const { playgroundPage } = + await publishBasicPromptingAndOpenShareablePlayground(page, context); - 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 +40,32 @@ 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.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/store-shard-2.spec.ts b/src/frontend/tests/core/features/store-shard-2.spec.ts index 61512a2be2a3..f5d5a2138471 100644 --- a/src/frontend/tests/core/features/store-shard-2.spec.ts +++ b/src/frontend/tests/core/features/store-shard-2.spec.ts @@ -1,6 +1,6 @@ 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"; @@ -34,8 +34,7 @@ test( 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("Success! Your API Key has been saved.")).toBeVisible(); await page.getByTestId("button-store").click(); await page.waitForTimeout(1000); @@ -66,7 +65,7 @@ test( await safeClick("tag-selector-Vector Store"); await safeClick("tag-selector-Memory"); - await page.getByText("Basic RAG").isVisible(); + await expect(page.getByText("Basic RAG")).toBeVisible(); }, ); @@ -97,8 +96,7 @@ test("should share component with share button", async ({ page }) => { 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("Success! Your API Key has been saved.")).toBeVisible(); await page.waitForSelector('[data-testid="sidebar-search-input"]', { timeout: 100000, }); @@ -123,33 +121,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/toolModeGroup.spec.ts b/src/frontend/tests/core/features/toolModeGroup.spec.ts deleted file mode 100644 index 6962f412505a..000000000000 --- 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/integrations/Basic Prompting.spec.ts b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts index be3e3dad9da7..980fe48f0b07 100644 --- a/src/frontend/tests/core/integrations/Basic Prompting.spec.ts +++ b/src/frontend/tests/core/integrations/Basic Prompting.spec.ts @@ -1,32 +1,21 @@ -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"; 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 page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); await page @@ -36,8 +25,7 @@ withEventDeliveryModes( //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 +45,14 @@ withEventDeliveryModes( timeout: 100000, }); - await page.getByText("matey").last().isVisible(); - - 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(); - - await page.getByRole("gridcell").last().isVisible(); + await expect(page.getByText("matey").last()).toBeVisible(); + 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(); // 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 +63,6 @@ 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 ab45b2e6be86..bd2339d3bfa9 100644 --- a/src/frontend/tests/core/integrations/Blog Writer.spec.ts +++ b/src/frontend/tests/core/integrations/Blog Writer.spec.ts @@ -1,27 +1,17 @@ -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"; 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); @@ -64,8 +54,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 aed2d1c19408..e29ad5fa13cd 100644 --- a/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts +++ b/src/frontend/tests/core/integrations/Custom Component Generator.spec.ts @@ -1,10 +1,9 @@ -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"; withEventDeliveryModes( "Custom Component Generator", @@ -14,11 +13,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); diff --git a/src/frontend/tests/core/integrations/Document QA.spec.ts b/src/frontend/tests/core/integrations/Document QA.spec.ts index 821e4560f0bd..a32f89a9edd1 100644 --- a/src/frontend/tests/core/integrations/Document QA.spec.ts +++ b/src/frontend/tests/core/integrations/Document QA.spec.ts @@ -1,28 +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 { 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"; 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,9 +21,7 @@ withEventDeliveryModes( await page.waitForSelector('[data-testid="button_run_chat output"]', { timeout: 3000, }); - - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); await page @@ -56,7 +45,9 @@ 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 1b0e277fab75..000000000000 --- 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 d8a9358fdbe0..000000000000 --- 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 6698a5736242..000000000000 --- 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 effdff30aa19..000000000000 --- 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 6595dae9db48..d514f1578b1f 100644 --- a/src/frontend/tests/core/integrations/Instagram Copywriter.spec.ts +++ b/src/frontend/tests/core/integrations/Instagram Copywriter.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 { 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"; 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, diff --git a/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts b/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts index 4435fde626d7..ec78ef7eeab2 100644 --- a/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts +++ b/src/frontend/tests/core/integrations/Invoice Summarizer.spec.ts @@ -1,9 +1,8 @@ -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"; test( "Invoice Summarizer", @@ -15,15 +14,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); diff --git a/src/frontend/tests/core/integrations/Market Research.spec.ts b/src/frontend/tests/core/integrations/Market Research.spec.ts index 09b6d54fdfb6..3b9d92735480 100644 --- a/src/frontend/tests/core/integrations/Market Research.spec.ts +++ b/src/frontend/tests/core/integrations/Market Research.spec.ts @@ -1,36 +1,26 @@ -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"; 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, diff --git a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts index 48469eedb6cc..619647a13d66 100644 --- a/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts +++ b/src/frontend/tests/core/integrations/Memory Chatbot.spec.ts @@ -1,31 +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 { 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"; 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 buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); diff --git a/src/frontend/tests/core/integrations/News Aggregator.spec.ts b/src/frontend/tests/core/integrations/News Aggregator.spec.ts index aef05e3db4c6..7078a586317c 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 cdd88d34760e..24b233324d14 100644 --- a/src/frontend/tests/core/integrations/Pokedex Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Pokedex Agent.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"; 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") 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 6bc0fd057c50..000000000000 --- 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 a2cfe4b23f04..000000000000 --- 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 f2d3311bcb2a..ef85d97e4ce7 100644 --- a/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts +++ b/src/frontend/tests/core/integrations/Prompt Chaining.spec.ts @@ -1,30 +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 { 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"; 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, 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 c6085a4c5270..876de7c8cc34 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,17 @@ -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"; 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(); 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 19effda57704..e8d9a1ec2aaf 100644 --- a/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts +++ b/src/frontend/tests/core/integrations/SEO Keyword Generator.spec.ts @@ -1,39 +1,28 @@ -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"; 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 page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); await page diff --git a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts index 28a1853ac5c1..7382e816ea34 100644 --- a/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts +++ b/src/frontend/tests/core/integrations/SaaS Pricing.spec.ts @@ -1,39 +1,28 @@ -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"; 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 page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); await page 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 5c09b4d0c2a9..000000000000 --- 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 2f2f64309d20..acc5bd5e3b72 100644 --- a/src/frontend/tests/core/integrations/Simple Agent Memory.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent Memory.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 { initialGPTsetup } from "../../utils/initialGPTsetup"; import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; 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 diff --git a/src/frontend/tests/core/integrations/Simple Agent.spec.ts b/src/frontend/tests/core/integrations/Simple Agent.spec.ts index e4afd32f7c12..43c81b5ac570 100644 --- a/src/frontend/tests/core/integrations/Simple Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Simple Agent.spec.ts @@ -1,28 +1,17 @@ -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( "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(); 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 7fbbce71f62a..1198577e39ae 100644 --- a/src/frontend/tests/core/integrations/Social Media Agent.spec.ts +++ b/src/frontend/tests/core/integrations/Social Media Agent.spec.ts @@ -1,9 +1,8 @@ -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"; function getRandomSocialMediaQuery(): string { const companies = [ @@ -61,15 +60,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); 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 c222d0850936..ead55b7c92e2 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,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 { 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"; 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(); 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 682f2c00fe27..faf019d5fd9a 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,21 @@ 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"; 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); 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 a53ec0fcd4ad..b6664e2d33d4 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,19 @@ -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"; 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,9 +31,7 @@ withEventDeliveryModes( await page.getByTestId("title-Chat Output").click(); await page.getByTestId("icon-MoreHorizontal").click(); await page.getByText("Expand").click(); - - await page.getByTestId("button_run_chat output").click(); - await page.waitForSelector("text=built successfully", { timeout: 120000 }); + await buildFlowAndWait(page); await page.getByRole("button", { name: "Playground", exact: true }).click(); await page diff --git a/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts b/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts index 78ed65f7e820..459fab10ef7b 100644 --- a/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts +++ b/src/frontend/tests/core/integrations/Youtube Analysis.spec.ts @@ -1,33 +1,22 @@ -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( "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, 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 a9a5d09ed3f2..98423d7a2b9b 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 f7f35deaaa26..87bde3d64fff 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/textInputOutput.spec.ts b/src/frontend/tests/core/integrations/textInputOutput.spec.ts deleted file mode 100644 index 58fd1cb84348..000000000000 --- 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/generalBugs-prompt.spec.ts b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts index f9a63f286e6f..5081b6a0b4c8 100644 --- a/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-prompt.spec.ts @@ -1,21 +1,15 @@ -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"; 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(); 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 dac10e89040c..0a930efe234b 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-5.spec.ts @@ -274,8 +274,7 @@ test( .first() .click(); - await page.getByText("Component Output").isVisible(); - + await expect(page.getByText("Component Output")).toBeVisible(); const text = await page.getByPlaceholder("Empty").textContent(); const permutations = [ 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 08d436f79173..b0c203a65fa7 100644 --- a/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts +++ b/src/frontend/tests/core/regression/generalBugs-shard-9.spec.ts @@ -1,23 +1,17 @@ -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"; 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(); 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 d3492829b8ca..5e36c1ba7ade 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 8320f79d067f..a26dbeee8660 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,6 +2,7 @@ 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"; test.describe("Session Deletion Data Leakage Fix", () => { // Helper to send a message in the playground @@ -78,11 +79,7 @@ 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 @@ -145,11 +142,7 @@ 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 diff --git a/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts b/src/frontend/tests/core/unit/agentDefaultToolsComponent.spec.ts index 4ee5fe6de653..fa4602e4c21e 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 c2e9843b8053..70e0b5811ced 100644 --- a/src/frontend/tests/core/unit/chatInputOutput.spec.ts +++ b/src/frontend/tests/core/unit/chatInputOutput.spec.ts @@ -1,15 +1,9 @@ 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("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", }); diff --git a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts index 203f3ad0dc6b..0fe8fd8e4e03 100644 --- a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts @@ -1,18 +1,12 @@ import { expect } from "@playwright/test"; import { test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; 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(); diff --git a/src/frontend/tests/core/unit/dropdownComponent.spec.ts b/src/frontend/tests/core/unit/dropdownComponent.spec.ts index 8a7509c81dd0..540154d2315b 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,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; 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 +146,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." diff --git a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts index c1715751d75c..ed37355b9f08 100644 --- a/src/frontend/tests/core/unit/fileUploadComponent.spec.ts +++ b/src/frontend/tests/core/unit/fileUploadComponent.spec.ts @@ -11,6 +11,7 @@ import { enableInspectPanel, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; // Run tests in this file serially to avoid database conflicts with shared file state test.describe.configure({ mode: "serial" }); @@ -30,13 +31,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); @@ -445,13 +440,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); diff --git a/src/frontend/tests/core/unit/floatComponent.spec.ts b/src/frontend/tests/core/unit/floatComponent.spec.ts index d009c8e29eae..7b2470cf4d84 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 c37e6e90247e..5e30fe6cf977 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/intComponent.spec.ts b/src/frontend/tests/core/unit/intComponent.spec.ts index 8fe90bfd69ae..2f4713e40062 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,13 +7,9 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test("IntComponent", { 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("openai"); diff --git a/src/frontend/tests/core/unit/keyPairListComponent.spec.ts b/src/frontend/tests/core/unit/keyPairListComponent.spec.ts index b5aa96716866..099cf88af5fb 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 075be0a8ba76..7fe0defdbc3b 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 dea2b3dacb85..69d7981cf830 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,12 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; 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"); diff --git a/src/frontend/tests/core/unit/promptModalComponent.spec.ts b/src/frontend/tests/core/unit/promptModalComponent.spec.ts index e29dfd7f3967..11679a1c2dc5 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,6 +8,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; import { unselectNodes } from "../../utils/unselect-nodes"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; // Helper function to verify prompt variables async function verifyPromptVariables( @@ -62,11 +62,7 @@ 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.waitForSelector( @@ -150,12 +146,7 @@ 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"); @@ -294,7 +285,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 +313,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,12 +375,7 @@ 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"); @@ -477,12 +463,7 @@ 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"); @@ -612,12 +593,7 @@ 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"); diff --git a/src/frontend/tests/core/unit/queryInputComponent.spec.ts b/src/frontend/tests/core/unit/queryInputComponent.spec.ts index eb5ef7242d8d..01cc98519f9f 100644 --- a/src/frontend/tests/core/unit/queryInputComponent.spec.ts +++ b/src/frontend/tests/core/unit/queryInputComponent.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 query input", @@ -130,23 +131,3 @@ 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; -} diff --git a/src/frontend/tests/core/unit/sliderComponent.spec.ts b/src/frontend/tests/core/unit/sliderComponent.spec.ts index 37e960d44d63..be23a17eb7cd 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 0e826e228423..46de387c685b 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 1174d74180fc..000000000000 --- 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 29f4d38c8035..f8394f2405dd 100644 --- a/src/frontend/tests/core/unit/textAreaModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/textAreaModalComponent.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( "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"); diff --git a/src/frontend/tests/core/unit/toggleComponent.spec.ts b/src/frontend/tests/core/unit/toggleComponent.spec.ts index f9c8eade8569..c38b860b8795 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 bdcf5ed46f6e..2addf5750122 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,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user should be able to create an api key within a webhook component", { tag: ["@release", "@workspace"] }, @@ -15,13 +15,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"); @@ -94,13 +88,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 3b5d882ba804..c7e0de04cf0b 100644 --- a/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts +++ b/src/frontend/tests/extended/features/actionsMainPage-shard-0.spec.ts @@ -35,8 +35,8 @@ test( 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,8 +45,8 @@ 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, 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 76b1a383422b..60c7f2a44a96 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 3cd95933cfbc..1fd1f9296721 100644 --- a/src/frontend/tests/extended/features/bulk-actions.spec.ts +++ b/src/frontend/tests/extended/features/bulk-actions.spec.ts @@ -19,7 +19,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,7 +28,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: "Basic Prompting" }).click(); @@ -37,7 +37,7 @@ test( }); 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, }); 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 1079568abad3..f364857b3df9 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,13 @@ 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"; 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", 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 6aca967603ed..000000000000 --- 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 9e13b0030e3b..5b8c33550dfe 100644 --- a/src/frontend/tests/extended/features/deleteComponents.spec.ts +++ b/src/frontend/tests/extended/features/deleteComponents.spec.ts @@ -1,18 +1,13 @@ -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"; 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(); @@ -25,7 +20,7 @@ test( .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("Success! Your API Key has been saved.")).toBeVisible(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -37,7 +32,7 @@ test( 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(); + await expect(page.getByText("Basic RAG").first()).toBeVisible(); await page.waitForSelector('[data-testid="home-dropdown-menu"]', { timeout: 100000, }); @@ -48,7 +43,7 @@ test( .isVisible(); await page.getByText("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 c6d7884611d7..3f80a86d6ffa 100644 --- a/src/frontend/tests/extended/features/deleteFlows.spec.ts +++ b/src/frontend/tests/extended/features/deleteFlows.spec.ts @@ -1,19 +1,13 @@ -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"; 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); @@ -31,8 +25,7 @@ test( 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("Success! Your API Key has been saved.")).toBeVisible(); await page.waitForSelector('[data-testid="button-store"]', { timeout: 30000, }); @@ -57,8 +50,7 @@ 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); @@ -69,7 +61,7 @@ test( .isVisible(); await page.getByText("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/edit-name-description-node.spec.ts b/src/frontend/tests/extended/features/edit-name-description-node.spec.ts index 3e5909057a9b..ae7ab7d343a3 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 f0bd61841022..5ee1f831f02a 100644 --- a/src/frontend/tests/extended/features/edit-tools.spec.ts +++ b/src/frontend/tests/extended/features/edit-tools.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 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"); diff --git a/src/frontend/tests/extended/features/files-page.spec.ts b/src/frontend/tests/extended/features/files-page.spec.ts index e540f836763d..bee2eac2258e 100644 --- a/src/frontend/tests/extended/features/files-page.spec.ts +++ b/src/frontend/tests/extended/features/files-page.spec.ts @@ -404,9 +404,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"); 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 f8afe910d5b3..000000000000 --- 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 d464ff90e211..b06008501585 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 6fe25e0931fe..8c45bf7bd040 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 f75293651d00..c234a472b92c 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 00d75312c5e0..9dbdcdf78a6b 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,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,6 +8,8 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; test( "user should not be able to upload a file larger than the limit", { tag: ["@release", "@api", "@database"] }, @@ -28,15 +28,8 @@ 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(); diff --git a/src/frontend/tests/extended/features/lock-flow.spec.ts b/src/frontend/tests/extended/features/lock-flow.spec.ts index ed7e3171110c..9b15330443d1 100644 --- a/src/frontend/tests/extended/features/lock-flow.spec.ts +++ b/src/frontend/tests/extended/features/lock-flow.spec.ts @@ -1,25 +1,18 @@ 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"; 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(); diff --git a/src/frontend/tests/extended/features/loop-component.spec.ts b/src/frontend/tests/extended/features/loop-component.spec.ts index 9961c2ac5f01..634def9125bf 100644 --- a/src/frontend/tests/extended/features/loop-component.spec.ts +++ b/src/frontend/tests/extended/features/loop-component.spec.ts @@ -1,16 +1,15 @@ 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"; 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); diff --git a/src/frontend/tests/extended/features/mcp-server.spec.ts b/src/frontend/tests/extended/features/mcp-server.spec.ts index 97529b8220b9..960d9210a26d 100644 --- a/src/frontend/tests/extended/features/mcp-server.spec.ts +++ b/src/frontend/tests/extended/features/mcp-server.spec.ts @@ -3,19 +3,14 @@ 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"; 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"]', @@ -198,12 +193,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 +325,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"]', @@ -485,12 +470,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"]', @@ -675,13 +655,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"]', @@ -996,12 +970,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 1a2974417253..39125bca65eb 100644 --- a/src/frontend/tests/extended/features/minimize.spec.ts +++ b/src/frontend/tests/extended/features/minimize.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 { zoomOut } from "../../utils/zoom-out"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; 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"); diff --git a/src/frontend/tests/extended/features/notifications.spec.ts b/src/frontend/tests/extended/features/notifications.spec.ts index af291bc85408..7458d5e08079 100644 --- a/src/frontend/tests/extended/features/notifications.spec.ts +++ b/src/frontend/tests/extended/features/notifications.spec.ts @@ -1,12 +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 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", diff --git a/src/frontend/tests/extended/features/outdated-actions.spec.ts b/src/frontend/tests/extended/features/outdated-actions.spec.ts index 7e57c607e666..c11a1c8abdfa 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 dc79a65d0a6e..7268a0f70a38 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,12 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; 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, @@ -77,9 +75,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, 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 3ae1afdfed9e..efa7613673e8 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 9f6ef06230b8..ca4da1f8c464 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 1d81f26eeab5..b85f654e5077 100644 --- a/src/frontend/tests/extended/features/sticky-notes.spec.ts +++ b/src/frontend/tests/extended/features/sticky-notes.spec.ts @@ -1,8 +1,8 @@ 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 interact with sticky notes", @@ -35,13 +35,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"]'); 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 5a15cae7b0c7..15985a7fd3d4 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,12 @@ 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 = ` +const SLEEP_60_CUSTOM_COMPONENT = ` # from langflow.field_typing import Data from langflow.custom import Component from langflow.io import MessageTextInput, Output @@ -82,14 +35,42 @@ 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.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 page.getByText("Check & Save").last().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 +83,30 @@ 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.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 page.getByTestId("button-stop").last().click(); + await expect(page.getByTestId(TID.buttonStop).last()).toBeVisible(); + await page.getByTestId(TID.buttonStop).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 d9b2586505d8..f2a68f1bb4ef 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 8ceaae4f4eb4..dfda52950e70 100644 --- a/src/frontend/tests/extended/features/store-shard-1.spec.ts +++ b/src/frontend/tests/extended/features/store-shard-1.spec.ts @@ -1,19 +1,14 @@ -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"; 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(); @@ -28,13 +23,13 @@ test.skip( .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("Success! Your API Key has been saved.")).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 +42,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 +52,25 @@ 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 expect(page.getByText("Website Content QA").first()).toBeVisible(); await page.getByText("Components").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("Basic RAG").first()).toBeVisible(); }, ); @@ -87,15 +78,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); @@ -114,8 +98,7 @@ test.skip( 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("Success! Your API Key has been saved.")).toBeVisible(); await page.waitForTimeout(1000); await page.getByTestId("button-store").click(); @@ -126,14 +109,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("Basic RAG")).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 3a6988b628f7..781208ecc8d9 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,15 @@ -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"; 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); @@ -83,13 +75,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"]', { @@ -179,8 +166,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 f91d3ac72c59..067418e19203 100644 --- a/src/frontend/tests/extended/features/tool-mode.spec.ts +++ b/src/frontend/tests/extended/features/tool-mode.spec.ts @@ -1,15 +1,14 @@ 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 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", diff --git a/src/frontend/tests/extended/features/twoEdges.spec.ts b/src/frontend/tests/extended/features/twoEdges.spec.ts index 30d59e0548bc..3a796153ac76 100644 --- a/src/frontend/tests/extended/features/twoEdges.spec.ts +++ b/src/frontend/tests/extended/features/twoEdges.spec.ts @@ -1,4 +1,4 @@ -import { test } from "../../fixtures"; +import { expect, test } from "../../fixtures"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( @@ -8,8 +8,8 @@ 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(); + await expect(page.getByText("Retriever", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("Search Results", { exact: true }).first()).toBeVisible(); await page.getByTestId("canvas_controls_dropdown").click(); const focusElementsOnBoard = async ({ page }) => { @@ -24,11 +24,10 @@ test( 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 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("Retriever", { exact: true }).first()).toBeVisible(); + await expect(page.getByText("Search Results", { exact: true }).first()).toBeVisible(); + await expect(page.getByTestId("icon-EyeOff").nth(0)).toBeVisible(); }, ); 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 8806c3c7ea7d..6a11c061138f 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,9 +1,9 @@ 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"; test( "user should be able to see errors on popups when raise an error", @@ -15,7 +15,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 +43,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"]', 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 ad9a469c06f0..257cc0717298 100644 --- a/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts +++ b/src/frontend/tests/extended/integrations/chatInputOutputUser-shard-1.spec.ts @@ -1,24 +1,17 @@ -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"; 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(); 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 1ca983518786..125b697eca8a 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,19 +8,14 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { skipIfMissing } from "../../utils/env/skip-if-missing"; +import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; 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(); 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 c290c7d04529..000000000000 --- 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 e104f3e3c9a2..92cb1c550a7c 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.waitForSelector("text=Finished", { - timeout: 5000, - }); + await page.getByTestId(TID.buttonSend).click(); - 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 a1206eefbc3c..8706ac13aea2 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", - ); - - if (!process.env.CI) { - dotenv.config({ path: path.resolve(__dirname, "../../.env") }); - } - await awaitBootstrapTest(page); + skipIfMissing.anthropicKey(); + loadDotenvIfLocal(__dirname); - 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.waitForSelector("text=chain.png", { timeout: 30000 }); + await page.getByTestId(TID.buttonSend).click(); - await page.getByText("chain.png").isVisible(); + await page.waitForSelector("text=chain.png", { + timeout: TIMEOUTS.standard, + }); - 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 750fd73753f6..bf963d40baf3 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,21 +1,15 @@ -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"; 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(); 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 2a5439582be6..42f01437993c 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,7 @@ import { openAdvancedOptions, } from "../../utils/open-advanced-options"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user must be able to see api key in webhook component when auto login is disabled", { tag: ["@release"] }, @@ -103,13 +104,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"); 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 aac55443f206..9fcda6873be8 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,5 +1,5 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; +import { openBlankFlow } from "../../utils/flow/open-blank-flow"; test( "user must be able to use a component with undefined replacement", @@ -7,13 +7,7 @@ test( 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 +21,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." 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 a747e02cd628..1b360341b7fd 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"; 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 170c49884060..4298ac469e4d 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,14 @@ 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"; 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); 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 3bbb93f27956..2e4e00fc45c1 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,20 +6,15 @@ 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"; 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(); 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 68014ca3e52d..659006d74ba3 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,21 +1,14 @@ -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"; 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(); 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 7b369170c956..000000000000 --- 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 5d95c0bc424b..a997120c2ad6 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-1.spec.ts @@ -1,20 +1,15 @@ -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"; 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(); @@ -39,6 +34,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 6ca48da45e28..c343a60a821f 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-10.spec.ts @@ -1,23 +1,16 @@ -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"; 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"; 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 712e0686a454..d455ae0869d3 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 3061ecef68cc..7408361189d2 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts @@ -1,21 +1,14 @@ -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"; 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(); 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 c0520a6d8be5..84d90e346fa2 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-3.spec.ts @@ -1,9 +1,9 @@ -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"; test( "should copy code from playground modal", @@ -11,21 +11,9 @@ test( 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, }); @@ -132,13 +120,7 @@ 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()); 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 8e8cf67ae2fb..47bec98fccff 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-6.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; test( "should be able to see error when something goes wrong on Code Modal", @@ -13,14 +12,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"]', 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 06d8a8c3b82c..f977704edbdd 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 6508208b1f46..cb6fa6abc9c8 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 c226264c0a76..000000000000 --- 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-new-api-keys.ts b/src/frontend/tests/utils/add-new-api-keys.ts deleted file mode 100644 index 70508e01a1e0..000000000000 --- 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/auth-helpers.ts b/src/frontend/tests/utils/auth-helpers.ts deleted file mode 100644 index af5b6f143381..000000000000 --- 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 000000000000..76aa53e70e27 --- /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 000000000000..c80cc64e11ee --- /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/build-data-transfer.ts b/src/frontend/tests/utils/build-data-transfer.ts deleted file mode 100644 index 133a95e4c013..000000000000 --- 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/constants/selectors.ts b/src/frontend/tests/utils/constants/selectors.ts new file mode 100644 index 000000000000..480701c7c284 --- /dev/null +++ b/src/frontend/tests/utils/constants/selectors.ts @@ -0,0 +1,6 @@ +/** + * 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 { 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 000000000000..20795cc1bd1d --- /dev/null +++ b/src/frontend/tests/utils/constants/testIds.ts @@ -0,0 +1,67 @@ +/** + * 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/timeouts.ts b/src/frontend/tests/utils/constants/timeouts.ts new file mode 100644 index 000000000000..b12679764a13 --- /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/evaluate-input-react-state-changes.ts b/src/frontend/tests/utils/evaluate-input-react-state-changes.ts deleted file mode 100644 index 97f2fcaf2ff8..000000000000 --- 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 000000000000..a361dc555d45 --- /dev/null +++ b/src/frontend/tests/utils/flow/add-component-from-sidebar.ts @@ -0,0 +1,50 @@ +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. */ + 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; +}; + +/** + * 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 }: 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) { + await page.getByTestId(testId).hover(); + await page + .getByTestId(`add-component-button-${testId.toLowerCase()}`) + .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 000000000000..8176b4675bee --- /dev/null +++ b/src/frontend/tests/utils/flow/build-flow-and-wait.ts @@ -0,0 +1,18 @@ +import type { Page } from "@playwright/test"; +import { TID } from "../constants/testIds"; +import { TIMEOUTS } from "../constants/timeouts"; + +/** + * 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=built successfully", { + 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 000000000000..6fd2ac47d22e --- /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 000000000000..606ca22ee0bc --- /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 000000000000..a818675aa0ee --- /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: "