chore: Upgrade to React 19.2.3 with compatibility fixes (#11100)

* feat(dependencies): add langchain-mcp-adapters to avoid incompatibility with langchain-core<1.0.0

* upgrade react 19

* fix icons not rendering

* test fixes

* fix shard 10

* new lock

* test(lock-flow.spec.ts): update edge deletion tests to use Backspace instead of Delete for consistency and reliability in edge removal

* [autofix.ci] apply automated fixes

* chore(pyproject.toml): remove langchain-mcp-adapters dependency to avoid incompatibility issues with langchain-core<1.0.0

* uv lock

* test(regression): increase wait times in general bugs reset flow tests to ensure stability and reliability of test execution

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Cristhian Zanforlin Lousa
2025-12-18 18:34:52 -03:00
committed by GitHub
parent 32f43e2018
commit f787c6715f
25 changed files with 1676 additions and 2734 deletions

File diff suppressed because it is too large Load Diff

View File

@ -4,24 +4,27 @@
"private": true,
"dependencies": {
"@chakra-ui/number-input": "^2.1.2",
"@headlessui/react": "^2.0.4",
"@chakra-ui/system": "^2.6.2",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@headlessui/react": "^2.2.9",
"@hookform/resolvers": "^3.6.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-form": "^0.1.8",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.3.7",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slider": "^1.2.1",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tabler/icons-react": "^3.6.0",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/line-clamp": "^0.4.4",
@ -29,14 +32,14 @@
"@types/axios": "^0.14.0",
"@xyflow/react": "^12.3.6",
"ace-builds": "^1.41.0",
"ag-grid-community": "^32.0.2",
"ag-grid-react": "^32.0.2",
"ag-grid-community": "^32.3.9",
"ag-grid-react": "^32.3.9",
"ansi-to-html": "^0.7.2",
"axios": "^1.7.4",
"base64-js": "^1.5.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"cmdk": "^1.1.1",
"dompurify": "^3.2.4",
"dotenv": "^16.4.5",
"elkjs": "^0.9.3",
@ -56,24 +59,23 @@
"pako": "^2.1.0",
"playwright": "^1.56.0",
"pretty-ms": "^9.1.0",
"react": "^18.3.1",
"react-ace": "^11.0.1",
"react": "^19.2.1",
"react-ace": "^14.0.1",
"react-cookie": "^7.1.4",
"react-dom": "^18.3.1",
"react-dom": "^19.2.1",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.52.0",
"react-hotkeys-hook": "^4.5.0",
"react-icons": "^5.2.1",
"react-laag": "^2.0.5",
"react-markdown": "^8.0.7",
"react-markdown": "^9.1.0",
"react-pdf": "^9.0.0",
"react-router-dom": "^6.23.1",
"react-sortablejs": "^6.1.4",
"react-syntax-highlighter": "^15.6.1",
"reactflow": "^11.11.3",
"rehype-mathjax": "^4.0.3",
"rehype-raw": "^6.1.1",
"remark-gfm": "3.0.1",
"rehype-mathjax": "^6.0.0",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"shadcn-ui": "^0.9.4",
"short-unique-id": "^5.2.0",
@ -127,23 +129,24 @@
"devDependencies": {
"@biomejs/biome": "2.1.1",
"@jest/types": "^30.0.1",
"@modelcontextprotocol/server-everything": "^0.6.2",
"@playwright/test": "^1.56.0",
"@storybook/addon-docs": "^10.1.0",
"@storybook/addon-links": "^10.1.0",
"@storybook/react": "^10.1.0",
"@storybook/react-vite": "^10.1.0",
"@modelcontextprotocol/server-everything": "^0.6.2",
"@playwright/test": "^1.56.0",
"@swc/cli": "^0.5.2",
"@swc/core": "^1.6.1",
"@tailwindcss/typography": "^0.5.13",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.1",
"@testing-library/user-event": "^14.5.2",
"@types/jest": "^29.5.12",
"@types/lodash": "4.17.5",
"@types/node": "^20.14.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/sortablejs": "^1.15.8",
"@types/uuid": "^9.0.8",
"@vitejs/plugin-react-swc": "^3.7.0",

View File

@ -76,12 +76,18 @@ export default function NodeDescription({
}
return (
<MemoizedMarkdown
linkTarget="_blank"
className={cn(
"markdown prose flex w-full flex-col leading-5 word-break-break-word [&_pre]:whitespace-break-spaces [&_pre]:!bg-code-description-background [&_pre_code]:!bg-code-description-background",
stickyNote ? "text-mmd" : "text-xs",
mdClassName,
)}
components={{
a: ({ node, ...props }) => (
<a {...props} target="_blank" rel="noopener noreferrer">
{props.children}
</a>
),
}}
>
{String(description)}
</MemoizedMarkdown>

View File

@ -30,13 +30,12 @@ export default function SingleAlert({
{dropItem.list.map((item, idx) => (
<li className="word-break-break-word" key={idx}>
<Markdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
className="align-text-top"
components={{
a: ({ node, ...props }) => (
<a
href={props.href}
{...props}
target="_blank"
className="underline"
rel="noopener noreferrer"

View File

@ -61,13 +61,12 @@ export default function ErrorAlert({
<li key={index} className="word-break-break-word">
<span className="">
<Markdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
className="align-text-top"
components={{
a: ({ node, ...props }) => (
<a
href={props.href}
{...props}
target="_blank"
className="underline"
rel="noopener noreferrer"

View File

@ -106,10 +106,26 @@ export const ForwardedIconComponent = memo(
<div className={className}></div>
);
// Check if TargetIcon is a valid React component (function, class, or lazy component)
// In React 19, lazy components have $$typeof Symbol, and forwardRef components have render property
const isValidComponent =
typeof TargetIcon === "function" ||
(typeof TargetIcon === "object" &&
TargetIcon !== null &&
// Check for various React component types:
// - $$typeof: lazy, forwardRef, memo components (Symbol.for('react.lazy'), etc.)
// - render: forwardRef components in some React versions
// - _payload: lazy component internals
// - type: wrapped components (memo wrapping forwardRef)
(TargetIcon.$$typeof ||
TargetIcon.render ||
TargetIcon._payload ||
TargetIcon.type));
return (
<Suspense fallback={skipFallback ? undefined : fallback}>
<ErrorBoundary onError={handleError}>
{TargetIcon?.render || TargetIcon?._payload ? (
{isValidComponent ? (
<TargetIcon
className={className}
style={style}
@ -146,15 +162,30 @@ export const ForwardedIconComponent = memo(
);
// Simple error boundary component for catching lazy load errors
class ErrorBoundary extends React.Component<{
children: React.ReactNode;
onError: () => void;
}> {
componentDidCatch(error: any) {
class ErrorBoundary extends React.Component<
{
children: React.ReactNode;
onError: () => void;
},
{ hasError: boolean }
> {
constructor(props: { children: React.ReactNode; onError: () => void }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(_error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error) {
this.props.onError();
}
render() {
if (this.state.hasError) {
return null;
}
return this.props.children;
}
}

View File

@ -171,7 +171,6 @@ export function ContentBlockDisplay({
<Markdown
className="text-sm font-semibold text-foreground"
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
rehypePlugins={[rehypeMathjax]}
components={{
p({ node, ...props }) {

View File

@ -55,10 +55,14 @@ export default function ContentDisplay({
<div className="ml-1 pr-20">
<Markdown
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
rehypePlugins={[rehypeMathjax]}
className="markdown prose max-w-full text-sm font-normal dark:prose-invert"
components={{
a: ({ node, ...props }) => (
<a {...props} target="_blank" rel="noopener noreferrer">
{props.children}
</a>
),
p({ node, ...props }) {
return (
<span className="block w-fit max-w-full">
@ -69,7 +73,8 @@ export default function ContentDisplay({
pre({ node, ...props }) {
return <>{props.children}</>;
},
code: ({ node, inline, className, children, ...props }) => {
code: ({ node, className, children, ...props }) => {
const inline = !(props as any).hasOwnProperty("data-language");
let content = children as string;
if (
Array.isArray(children) &&
@ -165,7 +170,10 @@ export default function ContentDisplay({
ul({ node, ...props }) {
return <ul className="max-w-full">{props.children}</ul>;
},
code: ({ node, inline, className, children, ...props }) => {
code: ({ node, className, children, ...props }) => {
const inline = !(props as any).hasOwnProperty(
"data-language",
);
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<SimplifiedCodeTabComponent

View File

@ -141,11 +141,14 @@ const DisclosureTrigger = memo(function DisclosureTrigger({
{React.Children.map(children, (child) => {
if (!React.isValidElement(child)) return child;
return React.cloneElement(child, {
const childElement = child as React.ReactElement<{
className?: string;
}>;
return React.cloneElement(childElement, {
...childProps,
className: cn(className, child.props.className),
...child.props,
});
className: cn(className, childElement.props.className),
...childElement.props,
} as React.Attributes);
})}
</>
);

View File

@ -35,7 +35,7 @@ export function TextLoop({
}: TextLoopProps) {
const [currentIndex, setCurrentIndex] = useState(0);
const items = Children.toArray(children);
const timerRef = useRef<NodeJS.Timeout>();
const timerRef = useRef<NodeJS.Timeout>(undefined);
const updateIndex = useCallback(() => {
setCurrentIndex((current) => {

View File

@ -2,8 +2,9 @@ import type React from "react";
import { forwardRef } from "react";
import { AIMLComponent } from "./AI-ML";
export const AIMLIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
return <AIMLComponent ref={ref} {...props} />;
},
);
export const AIMLIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{ className?: string }>
>((props, ref) => {
return <AIMLComponent ref={ref} {...props} />;
});

View File

@ -5,7 +5,7 @@ import { AthenaComponent } from "./athena";
export const AthenaIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
React.PropsWithChildren<{ className?: string }>
>((props, ref) => {
return <AthenaComponent ref={ref} {...props} />;
});

View File

@ -107,11 +107,12 @@ import { UpstashSvgIcon } from "@/icons/Upstash";
import { VectaraIcon } from "@/icons/VectaraIcon";
import { VertexAIIcon } from "@/icons/VertexAI";
import { VllmIcon } from "@/icons/vLLM";
import { VLMRunIcon } from "@/icons/VLMRun";
import { WeaviateIcon } from "@/icons/Weaviate";
import { WikipediaIcon } from "@/icons/Wikipedia";
import { WolframIcon } from "@/icons/Wolfram";
import { XAIIcon } from "@/icons/xAI";
import { YouTubeSvgIcon as YouTubeIcon } from "@/icons/Youtube";
import { YoutubeIcon as YouTubeIcon } from "@/icons/Youtube";
import { ZepMemoryIcon } from "@/icons/ZepMemory";
import { JigsawStackIcon } from "./JigsawStack";
import { WindsurfIcon } from "./Windsurf";

View File

@ -2,11 +2,9 @@ import type React from "react";
import { forwardRef } from "react";
import SvgFreezeAll from "./freezeAll";
("./freezeAll.jsx");
export const freezeAllIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
React.PropsWithChildren<{ className?: string }>
>((props, ref) => {
return <SvgFreezeAll ref={ref} {...props} />;
});

View File

@ -96,12 +96,11 @@ export const ErrorView = ({
{content.reason && (
<span className="">
<Markdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
components={{
a: ({ node, ...props }) => (
<a
href={props.href}
{...props}
target="_blank"
className="underline"
rel="noopener noreferrer"
@ -118,11 +117,13 @@ export const ErrorView = ({
},
code: ({
node,
inline,
className,
children,
...props
}) => {
const inline = !(
props as any
).hasOwnProperty("data-language");
let content = children as string;
if (
Array.isArray(children) &&

View File

@ -29,7 +29,6 @@ export const MarkdownField = ({
<div className="w-full items-baseline gap-2">
<Markdown
remarkPlugins={[remarkGfm as any]}
linkTarget="_blank"
rehypePlugins={[rehypeMathjax, rehypeRaw]}
className={cn(
"markdown prose flex w-full max-w-full flex-col items-baseline text-sm font-normal word-break-break-word dark:prose-invert",
@ -67,7 +66,8 @@ export const MarkdownField = ({
</div>
);
},
code: ({ node, inline, className, children, ...props }) => {
code: ({ node, className, children, ...props }) => {
const inline = !(props as any).hasOwnProperty("data-language");
let content = children as string;
if (
Array.isArray(children) &&

View File

@ -49,6 +49,7 @@ export default function ToolsTable({
const applyingSelection = useRef<boolean>(false);
const previousRowsCount = useRef<number>(0);
const skipSelectionReapply = useRef<number>(0);
const [isGridReady, setIsGridReady] = useState(false);
const { setOpen: setSidebarOpen } = useSidebar();
@ -59,7 +60,10 @@ export default function ToolsTable({
}, []);
useEffect(() => {
if (!open) return;
if (!open) {
setIsGridReady(false);
return;
}
previousRowsCount.current = rows.length;
const initialData = cloneDeep(rows).map((row, index) => ({
...row,
@ -93,7 +97,7 @@ export default function ToolsTable({
}, [rows]);
useEffect(() => {
if (!agGrid.current?.api || !selectedRows || !open) return;
if (!agGrid.current?.api || !selectedRows || !open || !isGridReady) return;
// Don't re-apply selection if we're just editing data fields (slug/description)
if (skipSelectionReapply.current > 0) {
@ -116,7 +120,7 @@ export default function ToolsTable({
setTimeout(() => {
applyingSelection.current = false;
}, 50);
}, [selectedRows, open]);
}, [selectedRows, open, isGridReady]);
useEffect(() => {
if (!open) {
@ -315,6 +319,10 @@ export default function ToolsTable({
setSidebarOpen(false);
};
const handleGridReady = () => {
setIsGridReady(true);
};
return (
<>
<main className="flex h-full w-full flex-1 flex-col gap-2 overflow-hidden py-4">
@ -344,6 +352,7 @@ export default function ToolsTable({
getRowId={getRowId}
pagination={true}
paginationPageSize={50}
onGridReady={handleGridReady}
/>
</div>
</main>

View File

@ -252,13 +252,12 @@ export default function FlowBuildingComponent() {
transition={{ duration: 0.2 }}
>
<Markdown
linkTarget="_blank"
remarkPlugins={[remarkGfm]}
className="my-1.5 align-text-top truncate-doubleline"
components={{
a: ({ node, ...props }) => (
<a
href={props.href}
{...props}
target="_blank"
className="underline"
rel="noopener noreferrer"

View File

@ -22,7 +22,7 @@ export type SingleAlertComponentType = {
};
export type AlertDropdownType = {
children: JSX.Element;
notificationRef?: React.RefObject<HTMLDivElement>;
notificationRef?: React.RefObject<HTMLDivElement | null>;
onClose?: () => void;
};
export type AlertItemType = {

View File

@ -1,6 +1,23 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-svgr/client" />
// React 19 compatibility - JSX namespace is now exported from React
import type { JSX } from "react";
declare global {
namespace JSX {
interface Element extends React.JSX.Element {}
interface ElementClass extends React.JSX.ElementClass {}
interface ElementAttributesProperty
extends React.JSX.ElementAttributesProperty {}
interface ElementChildrenAttribute
extends React.JSX.ElementChildrenAttribute {}
interface IntrinsicAttributes extends React.JSX.IntrinsicAttributes {}
interface IntrinsicClassAttributes<T>
extends React.JSX.IntrinsicClassAttributes<T> {}
interface IntrinsicElements extends React.JSX.IntrinsicElements {}
}
}
interface ImportMetaEnv {
readonly BACKEND_URL: string;
readonly ACCESS_TOKEN_EXPIRE_SECONDS: string;

View File

@ -43,11 +43,15 @@ test(
timeout: 20000,
});
await page.waitForSelector("text=OAUTH2", { timeout: 20000 });
await page.waitForTimeout(1000);
await page.getByTestId("button_open_list_selection").click();
await page.getByTestId(`list_item_fetch_emails`).click();
await page.getByTestId("int_int_max_results").fill("10");
await page.waitForTimeout(1000);
await page.getByTestId("button_run_gmail").click();
@ -55,9 +59,11 @@ test(
timeout: 30000,
});
await page.getByTestId("output-inspection-dataframe-gmailapi").click();
await page
.getByTestId("output-inspection-dataframe-composiogmailapicomponent")
.click();
const colNumber: number = await page.getByRole("gridcell").count();
expect(colNumber).toBeGreaterThan(9);
expect(colNumber).toBeGreaterThan(1);
},
);

View File

@ -136,29 +136,81 @@ test(
await page.getByLabel("Message logs").click();
await page.getByText("Page 1 of 1", { exact: true }).click();
// check rename session
await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("new name");
await page.mouse.move(0, 0);
await page.getByTestId("session-selector").first().hover();
await page
.getByTestId("session-selector")
.first()
.getByTestId("icon-MoreHorizontal")
.click();
await page.getByLabel("Rename").click();
await page
.getByTestId("session-selector")
.first()
.getByRole("textbox")
.fill("new name");
await page.getByTestId("icon-Check").click();
await page.waitForTimeout(500);
await page.getByTestId("session-selector").getByText("new name").click();
await page
.getByTestId("session-selector")
.getByText("new name")
.first()
.click();
// check cancel rename
await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("cancel name");
await page.getByTestId("session-selector").getByTestId("icon-X").click();
await page.getByTestId("session-selector").getByText("new name").click();
await page.mouse.move(0, 0);
await page.getByTestId("session-selector").first().hover();
await page
.getByTestId("session-selector")
.first()
.getByTestId("icon-MoreHorizontal")
.click();
await page.getByLabel("Rename").click();
await page
.getByTestId("session-selector")
.first()
.getByRole("textbox")
.fill("cancel name");
await page
.getByTestId("session-selector")
.first()
.getByTestId("icon-X")
.click();
await page
.getByTestId("session-selector")
.getByText("new name")
.first()
.click();
// check cancel rename blur
await page.getByRole("combobox").click();
await page.getByLabel("Rename").getByText("Rename").click();
await page.getByRole("textbox").fill("cancel_blur");
await page.mouse.move(0, 0);
await page.getByTestId("session-selector").first().hover();
await page
.getByTestId("session-selector")
.first()
.getByTestId("icon-MoreHorizontal")
.click();
await page.getByLabel("Rename").click();
await page
.getByTestId("session-selector")
.first()
.getByRole("textbox")
.fill("cancel_blur");
await page.getByText("PlaygroundChat").click();
await page.getByTestId("session-selector").getByText("new name").click();
await page
.getByTestId("session-selector")
.getByText("new name")
.first()
.click();
// check delete session
await page.getByRole("combobox").click();
await page.mouse.move(0, 0);
await page.getByTestId("session-selector").first().hover();
await page
.getByTestId("session-selector")
.first()
.getByTestId("icon-MoreHorizontal")
.click();
await page.getByLabel("Delete").click();
await page.getByRole("heading", { name: "New chat" }).click();
await page.getByTestId("new-chat").click();
// check new session
await page.getByTestId("input-chat-playground").click();
await page
@ -166,7 +218,7 @@ test(
.fill("session_after_delete");
await page.keyboard.press("Enter");
await page.getByTestId("chat-message-User-session_after_delete").click();
await expect(page.getByTestId("session-selector")).toBeVisible();
await expect(page.getByTestId("session-selector").first()).toBeVisible();
await page.waitForTimeout(500);

View File

@ -65,19 +65,28 @@ test(
await page.waitForTimeout(500);
await tryDeleteEdge(page);
await page.locator(".react-flow__edge-path").nth(0).click();
await page.keyboard.press("Delete");
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
await page.waitForTimeout(500);
// Delete edges one by one (when unlocked, should work)
await page.locator(".react-flow__edge").nth(0).click();
await page.waitForTimeout(200);
await page.keyboard.press("Backspace");
await page.waitForTimeout(300);
let numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(2);
await page.locator(".react-flow__edge-path").nth(0).click();
await page.keyboard.press("Delete");
numberOfEdges = await page.locator(".react-flow__edge-path").count();
await page.locator(".react-flow__edge").nth(0).click();
await page.waitForTimeout(200);
await page.keyboard.press("Backspace");
await page.waitForTimeout(300);
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(1);
await page.locator(".react-flow__edge-path").nth(0).click();
await page.keyboard.press("Delete");
numberOfEdges = await page.locator(".react-flow__edge-path").count();
await page.locator(".react-flow__edge").nth(0).click();
await page.waitForTimeout(200);
await page.keyboard.press("Backspace");
await page.waitForTimeout(300);
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(0);
await tryConnectNodes(page);
@ -100,7 +109,8 @@ test(
)
.click();
await page.getByTestId("handle-chatoutput-shownode-inputs-left").click();
numberOfEdges = await page.locator(".react-flow__edge-path").count();
await page.waitForTimeout(300);
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(3);
},
@ -110,7 +120,7 @@ async function tryConnectNodes(page: Page) {
await lockFlow(page);
const numberOfTries = 5;
let numberOfEdges = await page.locator(".react-flow__edge-path").count();
let numberOfEdges = await page.locator(".react-flow__edge").count();
for (let i = 0; i < numberOfTries; i++) {
try {
@ -118,7 +128,7 @@ async function tryConnectNodes(page: Page) {
timeout: 500,
});
} catch (_e) {
numberOfEdges = await page.locator(".react-flow__edge-path").count();
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(0);
}
@ -131,7 +141,7 @@ async function tryConnectNodes(page: Page) {
timeout: 500,
});
} catch (_e) {
numberOfEdges = await page.locator(".react-flow__edge-path").count();
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(0);
}
}
@ -141,20 +151,18 @@ async function tryConnectNodes(page: Page) {
async function tryDeleteEdge(page: Page) {
await lockFlow(page);
const numberOfEdges = await page.locator(".react-flow__edge-path").count();
let numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(3);
const numberOfTries = 5;
// When locked, clicking edges and pressing delete should not remove them
for (let i = 0; i < numberOfTries; i++) {
await expect(
page.locator(".react-flow__edge-path").nth(0).click({ timeout: 500 }),
).rejects.toThrow();
await expect(
page.locator(".react-flow__edge-path").nth(1).click({ timeout: 500 }),
).rejects.toThrow();
await expect(
page.locator(".react-flow__edge-path").nth(2).click({ timeout: 500 }),
).rejects.toThrow();
await page.locator(".react-flow__edge").nth(0).click();
await page.waitForTimeout(200);
await page.keyboard.press("Backspace");
await page.waitForTimeout(200);
numberOfEdges = await page.locator(".react-flow__edge").count();
expect(numberOfEdges).toBe(3);
}
await unlockFlow(page);

View File

@ -75,7 +75,7 @@ test(
await page.getByTestId("save-name-description-button").click();
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page
.getByTestId("handle-conditionalrouter-shownode-true-right")
@ -97,7 +97,7 @@ test(
await page.getByTestId("popover-anchor-input-input_text").fill("1");
await page.getByTestId("popover-anchor-input-match_text").fill("1");
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.getByTestId("button_run_text output").click();
@ -118,12 +118,12 @@ test(
// Now we will change the input to make the flow go through the other branch of the If-Else component
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.getByTestId("popover-anchor-input-input_text").fill("2");
await page.getByTestId("button_run_textoutputfalse").click();
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.waitForSelector("text=built successfully", { timeout: 30000 });
@ -142,14 +142,14 @@ test(
// retest to make sure we can run again the flow with the first branch of the If-Else component
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.getByTestId("popover-anchor-input-input_text").fill("1");
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.getByTestId("button_run_text output").click();
await page.waitForTimeout(500);
await page.waitForTimeout(2000);
await page.waitForSelector("text=built successfully", { timeout: 30000 });

View File

@ -57,12 +57,22 @@ test(
await page.getByTestId("playground-btn-flow-io").click();
// Wait for chat messages to be fully loaded/streamed
await page.waitForSelector('[data-testid="div-chat-message"]', {
timeout: 30000,
});
// Wait for streaming to complete
await page.waitForTimeout(1000);
const textContents = await page
.getByTestId("div-chat-message")
.allTextContents();
const concatAllText = textContents.join(" ");
// Ensure we captured a non-empty response
expect(concatAllText.length).toBeGreaterThan(0);
await page.getByText("Close").last().click();
await page.getByText("Prompt", { exact: true }).last().click();
@ -98,12 +108,18 @@ test(
await page.getByTestId("playground-btn-flow-io").click();
await page.waitForTimeout(500);
// Wait for chat messages to be fully loaded/streamed
await page.waitForSelector('[data-testid="div-chat-message"]', {
timeout: 30000,
});
// Wait for streaming to complete
await page.waitForTimeout(1000);
const textContents2 = await page
.getByTestId("div-chat-message")
.allTextContents();
// The frozen node should return the same cached output
textContents2.forEach((text) => {
expect(text).toBe(concatAllText);
});