mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-26 22:40:22 +08:00
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)
This commit is contained in:
@ -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();
|
||||
});
|
||||
|
||||
@ -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 },
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -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,
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
}) => {
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user