From ffd119cf3f60b3c26e2c2c321f85a4b1ba815092 Mon Sep 17 00:00:00 2001 From: Tarcio Date: Thu, 25 Jun 2026 10:28:28 -0300 Subject: [PATCH] test: remove duplicate Playwright e2e coverage (#13819) * test: remove duplicate test coverage (safe dedup) LE-1620 Tier 3 (phase A) - three duplicate-coverage removals, each verified to lose no unique assertion: Delete outdated-message.spec.ts: its only assertion (the "components need updates" banner) is identical to the first assertion of outdated-actions.spec.ts. output-modal-copy-button.spec.ts: drop the "JSON output from API Request component" sub-test (duplicated by copy-button-in-output.spec.ts); keep the text-output icon-state test. general-bugs-move-flow-from-folder.spec.ts: drop the happy-path "move flow from folder" test (covered more thoroughly by folders.spec.ts change-flow-folder); keep the unique stale-cache regression test. No coverage loss: each removed assertion has a home in a surviving test. Refs LE-1620 (Tier 3 / phase A) * test: fold minimize handle-visibility check into the 5-cycle test minimize.spec.ts checked a single minimize/expand cycle and uniquely asserted that the node connection handles get the no-show class. general-bugs-minimize-state-error already runs 5 cycles but only checked hide-node-content; this migrates the no-show handle assertion into its toggle helper, then deletes minimize.spec.ts. Refs LE-1620 (Tier 3 / C1) * test: merge store share-button test into generalBugs-shard-13 store-shard-2 and generalBugs-shard-13 both exercised share-on-canvas but checked different things: store-shard-2 validated the share modal contents (labels, public checkbox, category tags, name/description inputs); generalBugs-shard-13 checked idempotency (re-share + replace). Move the modal-validation test into generalBugs-shard-13 (guard adapted to skipIfMissing.storeApiKey + loadDotenvIfLocal), preserving every modal assertion. store-shard-2 keeps its filter-by-tag test. Refs LE-1620 (Tier 3 / M4) * test: actually trigger share before asserting success toast The store share test (moved here from store-shard-2 in this PR) asserted the "Flow shared successfully" toast without ever clicking the share confirm button, so it never exercised the real share. It went unnoticed because the test is STORE_API_KEY-gated and skipped in CI. Add the share-modal-button-flow click + replace handling after the modal validation, matching the existing share test. Refs LE-1620 (Tier 3 / M4 review) * test: assert modal checks instead of ignoring their booleans The API-keys warning and the public checkbox in the moved store share test called .isVisible()/.isChecked() but ignored the returned booleans, so they asserted nothing. Make them real web-first assertions. Pre-existing from store-shard-2; flagged in review. Refs LE-1620 (Tier 3 / M4 review) --- .../tests/core/features/store-shard-2.spec.ts | 86 ------------------ .../tests/extended/features/minimize.spec.ts | 72 --------------- .../features/outdated-message.spec.ts | 46 ---------- .../features/output-modal-copy-button.spec.ts | 57 ------------ .../general-bugs-minimize-state-error.spec.ts | 9 ++ ...general-bugs-move-flow-from-folder.spec.ts | 53 ----------- .../regression/generalBugs-shard-13.spec.ts | 87 +++++++++++++++++++ 7 files changed, 96 insertions(+), 314 deletions(-) delete mode 100644 src/frontend/tests/extended/features/minimize.spec.ts delete mode 100644 src/frontend/tests/extended/features/outdated-message.spec.ts 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 66d7b09676..60e0132aa7 100644 --- a/src/frontend/tests/core/features/store-shard-2.spec.ts +++ b/src/frontend/tests/core/features/store-shard-2.spec.ts @@ -1,9 +1,7 @@ import * as dotenv from "dotenv"; import path from "path"; import { expect, test } from "../../fixtures"; -import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { TEXTS } from "../../utils/constants/texts"; -import { renameFlow } from "../../utils/rename-flow"; test( "should filter by tag", @@ -69,87 +67,3 @@ test( await expect(page.getByText(TEXTS.templateBasicRag)).toBeVisible(); }, ); - -test("should share component with share button", 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") }); - } - - await page.goto("/"); - await page.waitForTimeout(1000); - - await page.getByTestId("button-store").click(); - await page.waitForTimeout(1000); - - await page.getByTestId("api-key-button-store").click({ - timeout: 200000, - }); - - await page - .getByPlaceholder(TEXTS.placeholderApiKey) - .fill(process.env.STORE_API_KEY ?? ""); - - await page.getByTestId("api-key-save-button-store").click(); - - await page.waitForTimeout(1000); - await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); - await page.waitForSelector('[data-testid="sidebar-search-input"]', { - timeout: 100000, - }); - - await page.getByTestId("icon-ChevronLeft").first().click(); - - await awaitBootstrapTest(page, { - skipGoto: true, - }); - - await page.waitForTimeout(1000); - - const randomName = Math.random().toString(36).substring(2); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: TEXTS.templateBasicPrompting }) - .click(); - - await renameFlow(page, { flowName: randomName }); - - await page.waitForSelector('[data-testid="shared-button-flow"]', { - timeout: 100000, - }); - - await page.getByTestId("shared-button-flow").first().click(); - 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 expect(page.getByText("Export").first()).toBeVisible(); - await expect(page.getByText("Share Flow").first()).toBeVisible(); - await page.waitForTimeout(3000); - - 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 expect(page.getByText(flowName).last()).toBeVisible(); - await expect(page.getByText(flowDescription).last()).toBeVisible(); - await page.waitForTimeout(1000); - - await expect(page.getByText("Flow shared successfully").last()).toBeVisible(); -}); diff --git a/src/frontend/tests/extended/features/minimize.spec.ts b/src/frontend/tests/extended/features/minimize.spec.ts deleted file mode 100644 index fd12048f94..0000000000 --- a/src/frontend/tests/extended/features/minimize.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { expect, test } from "../../fixtures"; -import { addLegacyComponents } from "../../utils/add-legacy-components"; -import { adjustScreenView } from "../../utils/adjust-screen-view"; -import { TEXTS } from "../../utils/constants/texts"; -import { openBlankFlow } from "../../utils/flow/open-blank-flow"; -import { zoomOut } from "../../utils/zoom-out"; - -test( - "user must be able to minimize and expand a component", - { tag: ["@release", "@workspace"] }, - async ({ page }) => { - await openBlankFlow(page); - - await addLegacyComponents(page); - - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill(TEXTS.searchTextInput); - await page.waitForSelector('[data-testid="input_outputText Input"]', { - timeout: 30000, - }); - - await page - .getByTestId("input_outputText Input") - .dragTo(page.locator('//*[@id="react-flow-id"]')); - - await page - .locator('//*[@id="react-flow-id"]') - .hover() - .then(async () => { - await page.mouse.down(); - await page.mouse.move(-800, 300); - }); - - await page.mouse.up(); - - await adjustScreenView(page); - - await zoomOut(page, 4); - - await page.getByTestId("more-options-modal").click(); - - await page.waitForSelector('[data-testid="minimize-button-modal"]', { - timeout: 10000, - }); - - await page.getByTestId("minimize-button-modal").first().click(); - - await expect( - page.locator(".react-flow__handle-left.no-show").first(), - ).toBeVisible({ timeout: 10000 }); - - await expect( - page.locator(".react-flow__handle-right.no-show").first(), - ).toBeVisible({ timeout: 10000 }); - - await page.getByTestId("more-options-modal").click(); - - await page.waitForSelector('[data-testid="expand-button-modal"]', { - timeout: 10000, - }); - - await page.getByTestId("expand-button-modal").first().click(); - - await expect(page.locator(".react-flow__handle-left").first()).toBeVisible({ - timeout: 10000, - }); - - await expect(page.locator(".react-flow__handle-right").first()).toBeVisible( - { timeout: 10000 }, - ); - }, -); diff --git a/src/frontend/tests/extended/features/outdated-message.spec.ts b/src/frontend/tests/extended/features/outdated-message.spec.ts deleted file mode 100644 index c8a4f660eb..0000000000 --- a/src/frontend/tests/extended/features/outdated-message.spec.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { readFileSync } from "fs"; -import { expect, test } from "../../fixtures"; -import { openFlowsList } from "../../utils/flow/open-flows-list"; - -test("user must be able outdated message on error", async ({ page }) => { - const dropTarget = await openFlowsList(page); - - await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); - // Read the asset and rename the flow uniquely so we can wait for THIS - // upload to appear in the list — avoids racing against the bootstrap-seeded - // "Basic Prompting" card or stale "Memory Chatbot" entries from sibling tests. - const rawJson = readFileSync("tests/assets/outdated_flow.json", "utf-8"); - const flowName = `Outdated Test Flow ${Date.now()}`; - const jsonContent = JSON.stringify({ - ...JSON.parse(rawJson), - name: flowName, - }); - - // 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], "outdated_flow.json", { - type: "application/json", - }); - dt.items.add(file); - return dt; - }, jsonContent); - - // Now dispatch - await dropTarget.dispatchEvent("drop", { - dataTransfer, - }); - - // Wait for the freshly-dropped flow card (by unique name) to appear, then click it. - const droppedCard = page - .getByTestId("list-card") - .filter({ hasText: flowName }); - await droppedCard.waitFor({ state: "visible", timeout: 30000 }); - await droppedCard.click(); - - // Verify the outdated components banner appears on the canvas. - await expect(page.getByText(/\d+ components? needs? updates?/)).toBeVisible({ - timeout: 30000, - }); -}); 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 f059123465..c284498110 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 @@ -78,61 +78,4 @@ test.describe("Output Modal Copy Button", () => { ).toBeVisible(); }, ); - - test( - "copy button should work with JSON output from API Request component", - { tag: ["@release", "@workspace"] }, - async ({ page }) => { - await openBlankFlow(page); - - await page.waitForSelector('[data-testid="disclosure-data sources"]', { - timeout: 3000, - state: "visible", - }); - - await page.getByTestId("disclosure-data sources").click(); - - await page - .getByTestId("data_sourceAPI Request") - .hover() - .then(async () => { - await page.getByTestId("add-component-button-api-request").click(); - - await page.waitForTimeout(500); - - await page - .getByTestId("popover-anchor-input-url_input") - .first() - .fill("https://httpbin.org/json"); - }); - - await page.getByTestId("button_run_api request").click(); - - await page.waitForSelector("text=Running", { - timeout: 30000, - state: "visible", - }); - - await page.waitForSelector(`text=${TEXTS.toastBuiltSuccessfully}`, { - timeout: 30000, - }); - - await page - .getByTestId("output-inspection-api response-apirequest") - .click(); - - await page.waitForSelector("text=Component Output", { timeout: 30000 }); - - // Verify the copy button exists and click it - const copyButton = page.getByTestId("copy-output-button"); - await expect(copyButton).toBeVisible(); - - await copyButton.click(); - - // Verify the success message appears - await page.waitForSelector("text=Copied to clipboard", { - timeout: 5000, - }); - }, - ); }); diff --git a/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts b/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts index 930555df52..f454dc4998 100644 --- a/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-minimize-state-error.spec.ts @@ -13,6 +13,15 @@ async function toggleNodeState(page: Page, action: "minimize" | "expand") { expect(await page.getByTestId("hide-node-content").count()).toBe( expectedCount, ); + // Minimizing also hides the node's connection handles via the `.no-show` + // class; expanding removes it. (Migrated from the former minimize.spec.ts.) + if (action === "minimize") { + await expect(page.locator(".react-flow__handle.no-show")).not.toHaveCount( + 0, + ); + } else { + await expect(page.locator(".react-flow__handle.no-show")).toHaveCount(0); + } } test( diff --git a/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts b/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts index 5b3e533af3..f2e77d1040 100644 --- a/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts +++ b/src/frontend/tests/extended/regression/general-bugs-move-flow-from-folder.spec.ts @@ -3,59 +3,6 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; import { TEXTS } from "../../utils/constants/texts"; import { renameFlow } from "../../utils/rename-flow"; -test("user must be able to move flow from folder", async ({ page }) => { - /* This is the original, happy-path regression: the destination - * project is BRAND NEW and has never been observed by React Query, - * so its `useGetFolder` cache doesn't exist and the first visit - * after the drop triggers a fresh fetch. It exercises drag-and-drop - * plumbing but does not exercise stale cache invalidation. */ - const randomName = Math.random().toString(36).substring(2, 15); - - await awaitBootstrapTest(page); - - await page.getByTestId("side_nav_options_all-templates").click(); - await page - .getByRole("heading", { name: TEXTS.templateBasicPrompting }) - .click(); - - await page.waitForTimeout(2000); - - await renameFlow(page, { flowName: randomName }); - - await page.waitForTimeout(2000); - - await page.getByTestId("icon-ChevronLeft").click(); - await page.waitForSelector('[data-testid="add-project-button"]', { - timeout: 3000, - }); - - await page.getByTestId("add-project-button").click(); - - //wait for the project to be created and changed to the new project - await page.waitForTimeout(2000); - - await page.getByTestId("sidebar-nav-Starter Project").click(); - - await page.waitForTimeout(2000); - - await page.getByText(randomName).hover(); - - await page - .getByTestId("list-card") - .first() - .dragTo(page.locator('//*[@id="sidebar-nav-New Project"]')); - - //wait for the drag and drop to be completed - await page.waitForTimeout(2000); - - await page.getByTestId("sidebar-nav-New Project").click(); - - await page.waitForSelector('[data-testid="list-card"]'); - - const flowNameCount = await page.getByText(randomName).count(); - expect(flowNameCount).toBeGreaterThan(0); -}); - test("moved flow must appear when destination project was visited while still empty", async ({ page, }) => { 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 e896138c46..20c03023c0 100644 --- a/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts +++ b/src/frontend/tests/extended/regression/generalBugs-shard-13.spec.ts @@ -4,6 +4,7 @@ import { TEXTS } from "../../utils/constants/texts"; import { loadDotenvIfLocal } from "../../utils/env/load-dotenv"; import { skipIfMissing } from "../../utils/env/skip-if-missing"; import { openTemplatesModal } from "../../utils/flow/new-project-flow"; +import { renameFlow } from "../../utils/rename-flow"; test( "should be able to share a component on the store by clicking on the share button on the canvas (requires store API key)", @@ -101,3 +102,89 @@ test( }); }, ); + +test("should share component with share button", async ({ page }) => { + skipIfMissing.storeApiKey(); + loadDotenvIfLocal(__dirname); + + await page.goto("/"); + await page.waitForTimeout(1000); + + await page.getByTestId("button-store").click(); + await page.waitForTimeout(1000); + + await page.getByTestId("api-key-button-store").click({ + timeout: 200000, + }); + + await page + .getByPlaceholder(TEXTS.placeholderApiKey) + .fill(process.env.STORE_API_KEY ?? ""); + + await page.getByTestId("api-key-save-button-store").click(); + + await page.waitForTimeout(1000); + await expect(page.getByText(TEXTS.toastApiKeySaved)).toBeVisible(); + await page.waitForSelector('[data-testid="sidebar-search-input"]', { + timeout: 100000, + }); + + await page.getByTestId("icon-ChevronLeft").first().click(); + + await awaitBootstrapTest(page, { + skipGoto: true, + }); + + await page.waitForTimeout(1000); + + const randomName = Math.random().toString(36).substring(2); + + await page.getByTestId("side_nav_options_all-templates").click(); + await page + .getByRole("heading", { name: TEXTS.templateBasicPrompting }) + .click(); + + await renameFlow(page, { flowName: randomName }); + + await page.waitForSelector('[data-testid="shared-button-flow"]', { + timeout: 100000, + }); + + await page.getByTestId("shared-button-flow").first().click(); + await expect(page.getByText("Name:")).toBeVisible(); + await expect(page.getByText("Description:")).toBeVisible(); + await expect(page.getByText("Set workflow status to public")).toBeVisible(); + await expect( + page.getByText( + "Attention: API keys in specified fields are automatically removed upon sharing.", + ), + ).toBeVisible(); + await expect(page.getByText("Export").first()).toBeVisible(); + await expect(page.getByText("Share Flow").first()).toBeVisible(); + await page.waitForTimeout(3000); + + 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 expect(page.getByTestId("public-checkbox")).toBeVisible(); + + const flowName = await page.getByTestId("input-flow-name").inputValue(); + const flowDescription = await page + .getByPlaceholder("Flow description") + .inputValue(); + await expect(page.getByText(flowName).last()).toBeVisible(); + await expect(page.getByText(flowDescription).last()).toBeVisible(); + await page.waitForTimeout(1000); + + // Trigger the actual share before asserting the success toast; re-sharing an + // already-published flow surfaces a replace confirmation. + await page.getByTestId("share-modal-button-flow").click(); + const replace = await page.getByTestId("replace-button").isVisible(); + if (replace) { + await page.getByTestId("replace-button").click(); + } + + await expect(page.getByText("Flow shared successfully").last()).toBeVisible(); +});