diff --git a/.github/workflows/typescript_test.yml b/.github/workflows/typescript_test.yml index 8b91001ca..07f86602a 100644 --- a/.github/workflows/typescript_test.yml +++ b/.github/workflows/typescript_test.yml @@ -1,6 +1,12 @@ name: Run Frontend Tests on: + workflow_dispatch: + inputs: + branch: + description: "(Optional) Branch to checkout" + required: false + type: string pull_request: merge_group: @@ -19,14 +25,18 @@ jobs: strategy: fail-fast: false matrix: - shardIndex: [1, 2, 3, 4, 5, 6, 7, 8] - shardTotal: [8] + shardIndex: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + shardTotal: [10] env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} STORE_API_KEY: ${{ secrets.STORE_API_KEY }} steps: - name: Checkout code uses: actions/checkout@v4 + with: + # If branch is passed as input, checkout that branch + # else checkout the default branch + ref: ${{ github.event.inputs.branch || github.ref }} - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/src/backend/base/langflow/api/v1/monitor.py b/src/backend/base/langflow/api/v1/monitor.py index f6c1fc4ac..224a26912 100644 --- a/src/backend/base/langflow/api/v1/monitor.py +++ b/src/backend/base/langflow/api/v1/monitor.py @@ -80,7 +80,8 @@ async def delete_messages( current_user: User = Depends(get_current_active_user), ): try: - session.exec(select(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore + session.exec(delete(MessageTable).where(MessageTable.id.in_(message_ids))) # type: ignore + session.commit() except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/src/backend/base/langflow/components/prototypes/ConditionalRouter.py b/src/backend/base/langflow/components/prototypes/ConditionalRouter.py index 07c8103f0..6d24bfa5e 100644 --- a/src/backend/base/langflow/components/prototypes/ConditionalRouter.py +++ b/src/backend/base/langflow/components/prototypes/ConditionalRouter.py @@ -70,7 +70,7 @@ class ConditionalRouterComponent(Component): return self.message else: self.stop("true_result") - return None + return None # type: ignore def false_response(self) -> Message: result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive) @@ -79,4 +79,4 @@ class ConditionalRouterComponent(Component): return self.message else: self.stop("false_result") - return None + return None # type: ignore diff --git a/src/backend/base/langflow/components/prototypes/__init__.py b/src/backend/base/langflow/components/prototypes/__init__.py index 89c14f7e9..252d1a1c5 100644 --- a/src/backend/base/langflow/components/prototypes/__init__.py +++ b/src/backend/base/langflow/components/prototypes/__init__.py @@ -2,7 +2,7 @@ from .ConditionalRouter import ConditionalRouterComponent from .FlowTool import FlowToolComponent from .Listen import ListenComponent from .Notify import NotifyComponent -from .Pass import PassComponent +from .Pass import PassMessageComponent from .PythonFunction import PythonFunctionComponent from .RunFlow import RunFlowComponent from .RunnableExecutor import RunnableExecComponent @@ -16,7 +16,7 @@ __all__ = [ "FlowToolComponent", "ListenComponent", "NotifyComponent", - "PassComponent", + "PassMessageComponent", "PythonFunctionComponent", "RunFlowComponent", "RunnableExecComponent", diff --git a/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx b/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx index 155592b78..7a969b965 100644 --- a/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx @@ -9,7 +9,10 @@ const useIconStatus = ( buildStatus: BuildStatus | undefined, validationStatus: VertexBuildTypeAPI | null, ) => { - const conditionSuccess = validationStatus && validationStatus.valid; + const conditionSuccess = + !(!buildStatus || buildStatus === BuildStatus.TO_BUILD) && + validationStatus && + validationStatus.valid; const conditionError = buildStatus === BuildStatus.ERROR; const conditionInactive = buildStatus === BuildStatus.INACTIVE; diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 4b22ac11e..a7c80ebd6 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -26,6 +26,7 @@ import { TabsTrigger, } from "../../components/ui/tabs"; import { LANGFLOW_SUPPORTED_TYPES } from "../../constants/constants"; +import getTabsOrder from "../../modals/apiModal/utils/get-tabs-order"; import { Case } from "../../shared/components/caseComponent"; import { useDarkStore } from "../../stores/darkStore"; import useFlowStore from "../../stores/flowStore"; @@ -56,6 +57,8 @@ export default function CodeTabsComponent({ setActiveTweaks, activeTweaks, allowExport = false, + isThereTweaks = false, + isThereWH = false, }: codeTabsPropsType) { const [isCopied, setIsCopied] = useState(false); const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null); @@ -93,6 +96,8 @@ export default function CodeTabsComponent({ return node.data.node.template[templateParam].type; }; + const tabsOrder = getTabsOrder(isThereWH, isThereTweaks); + return ( - {idx < 5 ? ( + {tabsOrder[idx].toLowerCase() !== "tweaks" ? (
{tab.description && (
- ) : idx === 5 ? ( + ) : tabsOrder[idx].toLowerCase() === "tweaks" ? ( <>
typeof row !== "object")) { + rows = rows.map((row) => ({ data: row })); + } + const columns = extractColumnsFromRows(rows, columnMode); const columnDefs = columns.map((col, idx) => ({ diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 3635f5f44..ebb5d1247 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1124,5 +1124,8 @@ export async function deleteMessagesFn(ids: string[]) { } export async function updateMessageApi(data: Message) { - return await api.post(`${BASE_URL_API}monitor/messages/${data.id}`, data); + if (data.files && typeof data.files === "string") { + data.files = JSON.parse(data.files); + } + return await api.put(`${BASE_URL_API}monitor/messages/${data.id}`, data); } diff --git a/src/frontend/src/modals/IOModal/components/SessionView/index.tsx b/src/frontend/src/modals/IOModal/components/SessionView/index.tsx index 70174ee72..21b64cfb1 100644 --- a/src/frontend/src/modals/IOModal/components/SessionView/index.tsx +++ b/src/frontend/src/modals/IOModal/components/SessionView/index.tsx @@ -52,7 +52,6 @@ export default function SessionView({ rows }: { rows: Array }) { ]} overlayNoRowsTemplate="No data available" onSelectionChanged={(event: SelectionChangedEvent) => { - console.log(event.api.getSelectedRows()); setSelectedRows(event.api.getSelectedRows().map((row) => row.id)); }} rowSelection="multiple" diff --git a/src/frontend/src/modals/apiModal/index.tsx b/src/frontend/src/modals/apiModal/index.tsx index 6b2a24680..7971d8288 100644 --- a/src/frontend/src/modals/apiModal/index.tsx +++ b/src/frontend/src/modals/apiModal/index.tsx @@ -44,11 +44,12 @@ const ApiModal = forwardRef( }, ref, ) => { + const tweaksCode = buildTweaks(flow); const tweak = useTweaksStore((state) => state.tweak); const addTweaks = useTweaksStore((state) => state.setTweak); const setTweaksList = useTweaksStore((state) => state.setTweaksList); const tweaksList = useTweaksStore((state) => state.tweaksList); - + const isThereTweaks = Object.keys(tweaksCode).length > 0; const [activeTweaks, setActiveTweaks] = useState(false); const { autoLogin } = useContext(AuthContext); const [open, setOpen] = @@ -82,7 +83,6 @@ const ApiModal = forwardRef( const pythonCode = getPythonCode(flow?.name, tweak); const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); const includeWebhook = flow.webhook; - const tweaksCode = buildTweaks(flow); const codesArray = [ runCurlCode, webhookCurlCode, @@ -121,7 +121,7 @@ const ApiModal = forwardRef( filterNodes(); - if (Object.keys(tweaksCode).length > 0) { + if (isThereTweaks) { setActiveTab("0"); setTabs(createTabsArray(codesArray, includeWebhook, true)); } else { @@ -215,7 +215,6 @@ const ApiModal = forwardRef( ); const pythonCode = getPythonCode(flow?.name, cloneTweak); const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin); - const isThereTweaks = Object.keys(tweaksCode).length > 0; const codesObj = getCodesObj({ runCurlCode, webhookCurlCode, @@ -251,6 +250,8 @@ const ApiModal = forwardRef( 0 + : false; + if ( + !isWrappedWithClass(e, "nocopy") && + (isWrappedWithClass(e, "react-flow__node") || multipleSelection) + ) { e.preventDefault(); (e as unknown as Event).stopImmediatePropagation(); if (window.getSelection()?.toString().length === 0 && lastSelection) { diff --git a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx index c3172f152..b2e654cef 100644 --- a/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/messagesPage/index.tsx @@ -4,6 +4,7 @@ import { ColGroupDef, SelectionChangedEvent, } from "ag-grid-community"; +import { cloneDeep } from "lodash"; import { useState } from "react"; import TableComponent from "../../../../components/tableComponent"; import useAlertStore from "../../../../stores/alertStore"; @@ -37,7 +38,7 @@ export default function MessagesPage() { function handleUpdateMessage(event: CellEditRequestEvent) { const newValue = event.newValue; const field = event.column.getColId(); - const row = event.data; + const row = cloneDeep(event.data); const data = { ...row, [field]: newValue, @@ -66,9 +67,7 @@ export default function MessagesPage() { ]} overlayNoRowsTemplate="No data available" onSelectionChanged={(event: SelectionChangedEvent) => { - setSelectedRows( - event.api.getSelectedRows().map((row) => row.index), - ); + setSelectedRows(event.api.getSelectedRows().map((row) => row.id)); }} rowSelection="multiple" suppressRowClickSelection={true} diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 4d9174f26..09324920f 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -695,6 +695,8 @@ type codeTabsFuncTempType = { }; export type codeTabsPropsType = { + isThereTweaks?: boolean; + isThereWH?: boolean; flow?: FlowType; tabs: Array; activeTab: string; diff --git a/src/frontend/src/types/messages/index.ts b/src/frontend/src/types/messages/index.ts index 4cb98a51d..9b19e1489 100644 --- a/src/frontend/src/types/messages/index.ts +++ b/src/frontend/src/types/messages/index.ts @@ -6,6 +6,7 @@ type Message = { sender_name: string; session_id: string; timestamp: string; + files: Array; id: string; }; diff --git a/src/frontend/src/utils/buildUtils.ts b/src/frontend/src/utils/buildUtils.ts index f3ae642bc..4d0a81594 100644 --- a/src/frontend/src/utils/buildUtils.ts +++ b/src/frontend/src/utils/buildUtils.ts @@ -99,6 +99,10 @@ export async function updateVerticesOrder( const runId = orderResponse.data.run_id; const verticesToRun = orderResponse.data.vertices_to_run; + useFlowStore + .getState() + .updateBuildStatus(verticesToRun, BuildStatus.TO_BUILD); + const verticesIds = orderResponse.data.ids; useFlowStore.getState().updateVerticesBuild({ verticesLayers, diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 9b62b2624..d16a7e851 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -1117,6 +1117,18 @@ export function updateProxyIdsOnTemplate( }); } +export function updateProxyIdsOnOutputs( + outputs: OutputFieldType[] | undefined, + idsMap: { [key: string]: string }, +) { + if (!outputs) return; + outputs.forEach((output) => { + if (output.proxy && idsMap[output.proxy.id]) { + output.proxy.id = idsMap[output.proxy.id]; + } + }); +} + export function updateEdgesIds( edges: Edge[], idsMap: { [key: string]: string }, @@ -1485,6 +1497,7 @@ export function updateGroupRecursion(groupNode: NodeType, edges: Edge[]) { let newFlow = groupNode.data.node!.flow; const idsMap = updateIds(newFlow.data!); updateProxyIdsOnTemplate(groupNode.data.node!.template, idsMap); + updateProxyIdsOnOutputs(groupNode.data.node.outputs, idsMap); let flowEdges = edges; updateEdgesIds(flowEdges, idsMap); } diff --git a/src/frontend/tests/end-to-end/Basic Prompting.ts b/src/frontend/tests/end-to-end/Basic Prompting.spec.ts similarity index 92% rename from src/frontend/tests/end-to-end/Basic Prompting.ts rename to src/frontend/tests/end-to-end/Basic Prompting.spec.ts index 0fdb56e41..aff45928c 100644 --- a/src/frontend/tests/end-to-end/Basic Prompting.ts +++ b/src/frontend/tests/end-to-end/Basic Prompting.spec.ts @@ -1,5 +1,4 @@ import { expect, test } from "@playwright/test"; -import path from "path"; test("Basic Prompting (Hello, World)", async ({ page }) => { if (!process?.env?.OPENAI_API_KEY) { @@ -67,10 +66,18 @@ test("Basic Prompting (Hello, World)", async ({ page }) => { .getByTestId("input-chat-playground") .last() .fill("Say hello as a pirate"); - await page.getByTestId("icon-LucideSend").last().click(); - await page.waitForTimeout(3000); - await page.getByText("Ahoy").last().isVisible(); + await page.waitForSelector('[data-testid="icon-LucideSend"]', { + timeout: 100000, + }); + + await page.getByTestId("icon-LucideSend").last().click(); + + await page.waitForSelector("text=matey", { + timeout: 100000, + }); + + await page.getByText("matey").last().isVisible(); await page.getByText("Default Session").last().click(); await page.getByText("timestamp", { exact: true }).last().isVisible(); diff --git a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts index 690a3b805..72c451c10 100644 --- a/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts +++ b/src/frontend/tests/end-to-end/chatInputOutputUser-shard-0.spec.ts @@ -80,6 +80,10 @@ test("user must be able to send an image on chat", async ({ page }) => { { fileContent }, ); + await page.waitForSelector('[data-testid="input-chat-playground"]', { + timeout: 100000, + }); + // Locate the target element const element = await page.getByTestId("input-chat-playground"); diff --git a/src/frontend/tests/end-to-end/generalBugs.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts similarity index 100% rename from src/frontend/tests/end-to-end/generalBugs.spec.ts rename to src/frontend/tests/end-to-end/generalBugs-shard-0.spec.ts diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts new file mode 100644 index 000000000..bd4c72e28 --- /dev/null +++ b/src/frontend/tests/end-to-end/generalBugs-shard-1.spec.ts @@ -0,0 +1,89 @@ +import { expect, test } from "@playwright/test"; +import * as dotenv from "dotenv"; +import path from "path"; + +test("should delete rows from table message", async ({ page }) => { + if (!process?.env?.OPENAI_API_KEY) { + //You must set the OPENAI_API_KEY on .env file to run this test + expect(false).toBe(true); + } + + await page.goto("/"); + await page.waitForTimeout(2000); + + let modalCount = 0; + try { + const modalTitleElement = await page?.getByTestId("modal-title"); + if (modalTitleElement) { + modalCount = await modalTitleElement.count(); + } + } catch (error) { + modalCount = 0; + } + + while (modalCount === 0) { + await page.getByText("New Project", { exact: true }).click(); + await page.waitForTimeout(5000); + modalCount = await page.getByTestId("modal-title")?.count(); + } + + await page.getByRole("heading", { name: "Basic Prompting" }).click(); + + await page.waitForSelector('[title="fit view"]', { + timeout: 100000, + }); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + await page + .getByTestId("popover-anchor-input-openai_api_key") + .fill(process.env.OPENAI_API_KEY ?? ""); + + await page.getByTestId("dropdown-model_name").click(); + await page.getByTestId("gpt-4o-0-option").click(); + + await page.waitForTimeout(2000); + + await page.getByTestId("button_run_chat output").click(); + await page.waitForSelector("text=built successfully", { timeout: 30000 }); + + await page.getByText("built successfully").last().click({ + timeout: 15000, + }); + + await page.getByText("Playground", { exact: true }).click(); + await page + .getByText("No input message provided.", { exact: true }) + .last() + .isVisible(); + + await page.waitForSelector('[data-testid="input-chat-playground"]', { + timeout: 100000, + }); + + await page + .getByTestId("input-chat-playground") + .last() + .fill("Say hello as a pirate"); + await page.getByTestId("icon-LucideSend").last().click(); + + await page.waitForSelector("text=matey", { + timeout: 100000, + }); + + await page.getByText("Close").last().click(); + await page.getByTestId("user-profile-settings").last().click(); + await page.getByText("Settings").last().click(); + await page.getByText("Messages").last().click(); + + const label = "Press Space to toggle all rows selection (unchecked)"; + await page.getByLabel(label).first().click(); + + await page.getByTestId("icon-Trash2").first().click(); + + await page.waitForSelector("text=No Data Available", { timeout: 30000 }); + await page.getByText("No Data Available").isVisible(); +}); diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-2.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-2.spec.ts new file mode 100644 index 000000000..fb5f5129b --- /dev/null +++ b/src/frontend/tests/end-to-end/generalBugs-shard-2.spec.ts @@ -0,0 +1,72 @@ +import { expect, test } from "@playwright/test"; + +test("should use webhook component on API", async ({ page }) => { + if (!process?.env?.OPENAI_API_KEY) { + //You must set the OPENAI_API_KEY on .env file to run this test + expect(false).toBe(true); + } + + await page.goto("/"); + await page.waitForTimeout(2000); + + let modalCount = 0; + try { + const modalTitleElement = await page?.getByTestId("modal-title"); + if (modalTitleElement) { + modalCount = await modalTitleElement.count(); + } + } catch (error) { + modalCount = 0; + } + + while (modalCount === 0) { + await page.getByText("New Project", { exact: true }).click(); + await page.waitForTimeout(5000); + modalCount = await page.getByTestId("modal-title")?.count(); + } + + await page.getByTestId("blank-flow").click(); + await page.waitForSelector('[data-testid="extended-disclosure"]', { + timeout: 30000, + }); + + await page.getByTestId("extended-disclosure").click(); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("webhook"); + + await page.waitForTimeout(1000); + + await page + .getByTestId("dataWebhook Input") + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.mouse.up(); + await page.mouse.down(); + + await page.waitForSelector('[title="fit view"]', { + timeout: 100000, + }); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + await page.waitForTimeout(2000); + await page.getByText("API", { exact: true }).click(); + + await page.getByText("Webhook cURL", { exact: true }).click(); + await page.getByRole("tab", { name: "Webhook 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); + expect(clipboardContent).toContain("curl -X POST"); + expect(clipboardContent).toContain("webhook"); + await page.getByRole("tab", { name: "Tweaks" }).click(); + // await page.getByText("Webhook Input").isVisible(); + // await page.getByText("Webhook Input").click(); +}); diff --git a/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts new file mode 100644 index 000000000..445c870d3 --- /dev/null +++ b/src/frontend/tests/end-to-end/generalBugs-shard-3.spec.ts @@ -0,0 +1,181 @@ +import { expect, test } from "@playwright/test"; + +test("should copy code from playground modal", async ({ page }) => { + await page.goto("/"); + await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); + await page.waitForTimeout(2000); + + let modalCount = 0; + try { + const modalTitleElement = await page?.getByTestId("modal-title"); + if (modalTitleElement) { + modalCount = await modalTitleElement.count(); + } + } catch (error) { + modalCount = 0; + } + + while (modalCount === 0) { + await page.getByText("New Project", { exact: true }).click(); + await page.waitForTimeout(5000); + modalCount = await page.getByTestId("modal-title")?.count(); + } + + await page.waitForSelector('[data-testid="blank-flow"]', { + timeout: 30000, + }); + + await page.getByTestId("blank-flow").click(); + await page.waitForSelector('[data-testid="extended-disclosure"]', { + timeout: 30000, + }); + await page.getByTestId("extended-disclosure").click(); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("chat output"); + await page.waitForTimeout(1000); + + await page + .getByTestId("outputsChat Output") + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.mouse.up(); + await page.mouse.down(); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("chat input"); + await page.waitForTimeout(1000); + + await page + .getByTestId("inputsChat Input") + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.mouse.up(); + await page.mouse.down(); + + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("openai"); + await page.waitForTimeout(1000); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + await page + .getByTestId("modelsOpenAI") + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.mouse.up(); + await page.mouse.down(); + + await page.waitForSelector('[title="fit view"]', { + timeout: 100000, + }); + + await page.getByTitle("fit view").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + await page.getByTitle("zoom out").click(); + + if (!process?.env?.OPENAI_API_KEY) { + //You must set the OPENAI_API_KEY on .env file to run this test + expect(false).toBe(true); + } + + await page + .getByTestId("popover-anchor-input-openai_api_key") + .fill(process.env.OPENAI_API_KEY ?? ""); + + const elementsChatInput = await page + .locator('[data-testid="handle-chatinput-shownode-message-right"]') + .all(); + + let visibleElementHandle; + + for (const element of elementsChatInput) { + if (await element.isVisible()) { + visibleElementHandle = element; + break; + } + } + + // Click and hold on the first element + await visibleElementHandle.hover(); + await page.mouse.down(); + + const elementsOpenAiInput = await page + .locator('[data-testid="handle-openaimodel-shownode-input-left"]') + .all(); + + for (const element of elementsOpenAiInput) { + if (await element.isVisible()) { + visibleElementHandle = element; + break; + } + } + + await visibleElementHandle.hover(); + await page.mouse.up(); + + const elementsOpenAiOutput = await page + .locator('[data-testid="handle-openaimodel-shownode-text-right"]') + .all(); + + for (const element of elementsOpenAiOutput) { + if (await element.isVisible()) { + visibleElementHandle = element; + break; + } + } + + // Click and hold on the first element + await visibleElementHandle.hover(); + await page.mouse.down(); + + // Move to the second element + const elementsChatOutput = await page + .getByTestId("handle-chatoutput-shownode-text-left") + .all(); + + for (const element of elementsChatOutput) { + if (await element.isVisible()) { + visibleElementHandle = element; + break; + } + } + + await visibleElementHandle.hover(); + await page.mouse.up(); + + await page.getByLabel("fit view").click(); + await page.getByText("Playground", { exact: true }).click(); + await page.waitForSelector('[data-testid="input-chat-playground"]', { + timeout: 100000, + }); + await page.getByTestId("input-chat-playground").click(); + await page + .getByTestId("input-chat-playground") + .fill("Could you provide a Python example for a 'Hello, World!' program?"); + + await page.waitForSelector('[data-testid="icon-LucideSend"]', { + timeout: 100000, + }); + + await page.getByTestId("icon-LucideSend").click(); + + await page.getByRole("tab", { name: "python" }).isVisible({ + timeout: 100000, + }); + + await page.getByTestId("icon-Copy").first().click(); + + const handle = await page.evaluateHandle(() => + navigator.clipboard.readText(), + ); + const clipboardContent = await handle.jsonValue(); + expect(clipboardContent.length).toBeGreaterThan(0); + expect(clipboardContent).toContain("Hello"); +}); diff --git a/src/frontend/tests/end-to-end/textInputOutput.spec.ts b/src/frontend/tests/end-to-end/textInputOutput.spec.ts index 7264a3af1..dc7e8c2d5 100644 --- a/src/frontend/tests/end-to-end/textInputOutput.spec.ts +++ b/src/frontend/tests/end-to-end/textInputOutput.spec.ts @@ -91,10 +91,6 @@ test("TextInputOutputComponent", async ({ page }) => { await visibleElementHandle.hover(); await page.mouse.down(); - const elementsOpenAiInput = await page.getByTestId( - "handle-openaimodel-shownode-input-left", - ); - for (const element of elementsTextInputOutput) { if (await element.isVisible()) { visibleElementHandle = element; diff --git a/src/frontend/tsconfig.json b/src/frontend/tsconfig.json index d713c77f7..28cdcd18b 100644 --- a/src/frontend/tsconfig.json +++ b/src/frontend/tsconfig.json @@ -34,7 +34,7 @@ "tests/end-to-end/floatComponent.spec.ts", "tests/end-to-end/flowPage.spec.ts", "tests/end-to-end/flowSettings.spec.ts", - "tests/end-to-end/generalBugs.spec.ts", + "tests/end-to-end/generalBugs-shard-0.spec.ts", "tests/end-to-end/globalVariables.spec.ts", "tests/end-to-end/group.spec.ts", "tests/end-to-end/folders.spec.ts",