feat: enhance APIRequestComponent with new output methods (#7148)

* ✨ (Research Translation Loop.spec.ts): Increase timeout value by a factor of 3 for better reliability in waiting for element to appear
✨ (chatInputOutputUser-shard-1.spec.ts): Increase timeout value by a factor of 3 for better reliability in waiting for element to appear

* 🐛 (typescript_test.yml): adjust the maximum shard count to 10 to prevent exceeding the limit and optimize test execution.

* 🐛 (chatInputOutputUser-shard-1.spec.ts): increase timeout for waiting for "built successfully" text to improve test reliability

* ⬆️ (typescript_test.yml): increase maximum shard count to 15 for better test distribution
♻️ (Portfolio Website Code Generator.spec.ts): refactor test assertions to improve readability and maintainability

* 🐛 (typescript_test.yml): adjust the maximum shard count to 10 to prevent exceeding the limit of parallel test executions

* 🔧 (typescript_test.yml): Increase maximum shard count to 15 for better test distribution efficiency
🐛 (chatInputOutputUser-shard-1.spec.ts): Update timeout values for page element waits to prevent premature failures due to timing issues

* templates adjustments

* travel planning fix

* Update Travel Planning Agents.json

* fix templates

* ♻️ (Youtube Analysis.spec.ts): remove unused imports and cleanup code for better readability and maintainability

* json fix

* fix: update simple agent template (#7081)

* Update Simple Agent.json

* Update Simple Agent.json

* feat: update search agent template agent component (#7082)

* update agent component with the latest changes

* Update Search agent.json

* Update Search agent.json

* feat: enhance APIRequestComponent with new output methods

- Refactored output methods to include `as_data` and `as_message` for better data handling.
- Updated existing output method from `make_requests` to `as_data` for consistency.
- Improved metadata handling by merging dictionary results and storing non-dict results as 'data'.
- Added detailed docstrings for new methods to clarify their functionality.

* added the updated test class

* [autofix.ci] apply automated fixes

* 📝 (ContentBlockDisplay.tsx): wrap headerIcon element in a span with data-testid attribute for better accessibility
📝 (DurationDisplay.tsx): add data-testid attribute to the duration display element for testing purposes
📝 (Simple Agent.spec.ts, Social Media Agent.spec.ts, generalBugs-shard-9.spec.ts): update test assertions to improve readability and accuracy
📝 (chatInputOutput.spec.ts): add a skip test annotation and a todo comment for further investigation

* ✨ (typescript_test.yml): Add support for a new development suite in the test workflow
✨ (frontend): Add support for a new development suite in multiple test files

* 🔧 (.github/workflows/typescript_test.yml): ensure that the SUITES variable is valid JSON format to prevent errors and improve reliability

* ✨ (inputFileComponent/index.tsx): Refactor InputFileComponent to wrap Button component in a div for better structure and readability
🔧 (Vector Store.spec.ts): Add initialGPTsetup function to set up GPT environment variables for tests
🔧 (Vector Store.spec.ts): Refactor test to use initialGPTsetup function and improve readability
🔧 (add-new-api-keys.ts): Refactor addNewApiKeys function to handle multiple openai_api_key inputs
🔧 (remove-old-api-keys.ts): Refactor removeOldApiKeys function to click on the correct remove-icon-badge element

* ✨ Add support for running tests in serial mode with a delay between each test
🔧 Configure tests to run with a 3-second delay between each test run
🔧 Add a 7-second delay before starting tests in userSettings.spec.ts
🔧 Add a 3-second delay before the second event delivery mode test in withEventDeliveryModes.ts

* 📝 (userSettings.spec.ts): remove unnecessary console log message to improve test readability and maintainability

* 📝 (deploy-dropdown.tsx): Add data-testid attribute to shareable-playground element
✨ (index.tsx): Add useGetTypes hook to fetch types data when component is fetched
🔧 (publish-flow.spec.ts): Increase timeout for page.waitForSelector and page.waitForTimeout
🔧 (Vector Store.spec.ts): Refactor code to wait for dropdown to appear and be visible
🔧 (files-page.spec.ts): Refactor tests to run serially with a delay between each test and add comments to improve readability

* ✨ Add useGetTypes hook to fetch types data and support caching with checkCache option
🔧 Refactor useGetTypes hook to accept options object with checkCache property
🔧 Refactor useGetTypes hook to conditionally return cached data if available
🔧 Refactor useGetTypes hook to fetch types data with force_refresh query parameter
🔧 Refactor useGetTypes hook to handle errors and set types data
🔧 Refactor useGetTypes hook to improve query function and options handling
🔧 Refactor useGetTypes hook to optimize query function and options handling
🔧 Refactor useGetTypes hook to improve caching logic and error handling
🔧 Refactor useGetTypes hook to enhance caching mechanism and error handling
🔧 Refactor useGetTypes hook to improve data fetching and error handling
🔧 Refactor useGetTypes hook to optimize data fetching and error handling

* ✨ (Vector Store.spec.ts): Remove unnecessary loadingOptions check and expectation
♻️ (withEventDeliveryModes.ts): Refactor withEventDeliveryModes function to accept a timeout parameter for better flexibility

* ⚡️ (Vector Store.spec.ts): increase timeout for page.waitForTimeout from 2000ms to 10000ms to improve test stability and reliability

* update pokedex agent template

* ✨ (publish-flow.spec.ts): Remove unnecessary development tag from test description
✨ (Simple Agent.spec.ts, Social Media Agent.spec.ts): Remove unnecessary development tag from test description
✨ (Vector Store.spec.ts): Change withEventDeliveryModes to test for better test organization
🔧 (chatInputOutput.spec.ts): Refactor test description and remove unnecessary development tag
✨ (files-page.spec.ts): Remove unnecessary development tag from test descriptions
✨ (userSettings.spec.ts): Remove unnecessary development tag from test description

* formatting json

* ✨ (Vector Store.spec.ts): Add new integration test withEventDeliveryModes for Vector Store RAG
🔧 (Vector Store.spec.ts): Update timeout values in test functions to improve test performance and reliability

* 📝 (Text Sentiment Analysis.json): Update JSON file to have consistent formatting and structure for output_types and inputTypes arrays
📝 (Text Sentiment Analysis.spec.ts): Refactor integration test for Text Sentiment Analysis to improve readability and maintainability

* ✨ (PageComponent/index.tsx): Update minZoom and maxZoom values for better user experience
🐛 (upload-file.ts): Fix missing await keyword before clicking on an element

* 🐛 (PageComponent/index.tsx): fix minZoom value to 0.2 for consistency with fitViewOptions and improve user experience

* ✨ (dropdownComponent.spec.ts): add delay before checking dropdown value to ensure it has updated properly
✨ (dropdownComponent.spec.ts): add delay before interacting with more options modal to ensure it has loaded
✨ (floatComponent.spec.ts): add delay after clicking add button to wait for API request to complete

---------

Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: Edwin Jose <edwin.jose@datastax.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Rodrigo Nader 2025-03-20 19:46:19 -03:00 • committed by GitHub
commit 44254206b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1183 additions and 727 deletions

View file

@ -11,6 +11,7 @@ import { ForwardedIconComponent } from "@/components/common/genericIconComponent
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
import { useGetTypes } from "@/controllers/API/queries/flows/use-get-types";
import BaseModal from "@/modals/baseModal";
import useAlertStore from "@/stores/alertStore";
import { useTypesStore } from "@/stores/typesStore";
@ -53,6 +54,7 @@ export default function GlobalVariableModal({
const { mutate: updateVariable } = usePatchGlobalVariables();
const { data: globalVariables } = useGetGlobalVariables();
const [availableFields, setAvailableFields] = useState<string[]>([]);
useGetTypes({ checkCache: true, enabled: !!globalVariables });
useEffect(() => {
if (globalVariables && componentFields.size > 0) {
@ -66,6 +68,8 @@ export default function GlobalVariableModal({
if (referenceField && fields.includes(referenceField)) {
setFields([referenceField]);
}
} else {
setAvailableFields(["System", "System Message", "System Prompt"]);
}
}, [globalVariables, componentFields, initialData]);

View file

@ -143,9 +143,9 @@ export default function PublishDropdown() {
!hasIO ? "cursor-not-allowed" : "",
"flex items-center",
)}
data-testid="shareable-playground"
>
<DropdownMenuItem
data-testid="shareable-playground"
disabled={!hasIO || !isPublished}
className="deploy-dropdown-item group flex-1"
onClick={() => {

View file

@ -56,14 +56,13 @@ const OptionBadge = ({
className={cn("flex items-center gap-1 truncate", className)}
>
<div className="truncate">{option}</div>
<div>
<X
className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive"
onClick={(e) =>
onRemove(e as unknown as React.MouseEvent<HTMLButtonElement>)
}
data-testid="remove-icon-badge"
/>
<div
data-testid="remove-icon-badge"
onClick={(e) =>
onRemove(e as unknown as React.MouseEvent<HTMLButtonElement>)
}
>
<X className="h-3 w-3 cursor-pointer bg-transparent hover:text-destructive" />
</div>
</Badge>
);

View file

@ -246,27 +246,31 @@ export default function InputFileComponent({
isList={isList}
>
{(selectedFiles.length === 0 || isList) && (
<Button
disabled={isDisabled}
variant={selectedFiles.length !== 0 ? "ghost" : "default"}
size={selectedFiles.length !== 0 ? "iconMd" : "default"}
className={cn(
selectedFiles.length !== 0 &&
"hit-area-icon absolute -top-8 right-0",
"font-semibold",
)}
data-testid="button_open_file_management"
>
{selectedFiles.length !== 0 ? (
<ForwardedIconComponent
name="Plus"
className="icon-size"
strokeWidth={ICON_STROKE_WIDTH}
/>
) : (
<div>Select file{isList ? "s" : ""}</div>
)}
</Button>
<div data-testid="input-file-component">
<Button
disabled={isDisabled}
variant={
selectedFiles.length !== 0 ? "ghost" : "default"
}
size={selectedFiles.length !== 0 ? "iconMd" : "default"}
className={cn(
selectedFiles.length !== 0 &&
"hit-area-icon absolute -top-8 right-0",
"font-semibold",
)}
data-testid="button_open_file_management"
>
{selectedFiles.length !== 0 ? (
<ForwardedIconComponent
name="Plus"
className="icon-size"
strokeWidth={ICON_STROKE_WIDTH}
/>
) : (
<div>Select file{isList ? "s" : ""}</div>
)}
</Button>
</div>
)}
</FileManagerModal>
</div>

View file

@ -5,18 +5,28 @@ import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
export const useGetTypes: useQueryFunctionType<undefined> = (options) => {
export const useGetTypes: useQueryFunctionType<
undefined,
any,
{ checkCache?: boolean }
> = (options) => {
const { query } = UseRequestProcessor();
const setLoading = useFlowsManagerStore((state) => state.setIsLoading);
const setTypes = useTypesStore((state) => state.setTypes);
const getTypesFn = async () => {
const getTypesFn = async (checkCache = false) => {
try {
if (checkCache) {
const data = useTypesStore.getState().types;
if (data && Object.keys(data).length > 0) {
return data;
}
}
const response = await api.get<APIObjectType>(
`${getURL("ALL")}?force_refresh=true`,
);
const data = response?.data;
console.log("[Types] Got types data:", data);
setTypes(data);
return data;
} catch (error) {
@ -26,10 +36,14 @@ export const useGetTypes: useQueryFunctionType<undefined> = (options) => {
}
};
const queryResult = query(["useGetTypes"], getTypesFn, {
refetchOnWindowFocus: false,
...options,
});
const queryResult = query(
["useGetTypes"],
() => getTypesFn(options?.checkCache),
{
refetchOnWindowFocus: false,
...options,
},
);
return queryResult;
};

View file

@ -1,7 +1,6 @@
import { useGetAutoLogin } from "@/controllers/API/queries/auth";
import { useGetConfig } from "@/controllers/API/queries/config/use-get-config";
import { useGetBasicExamplesQuery } from "@/controllers/API/queries/flows/use-get-basic-examples";
import { useGetTypes } from "@/controllers/API/queries/flows/use-get-types";
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
import { useGetTagsQuery } from "@/controllers/API/queries/store";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
@ -15,7 +14,6 @@ import { Outlet } from "react-router-dom";
import { LoadingPage } from "../LoadingPage";
export function AppInitPage() {
const dark = useDarkStore((state) => state.dark);
const refreshStars = useDarkStore((state) => state.refreshStars);
const isLoading = useFlowsManagerStore((state) => state.isLoading);

View file

@ -573,11 +573,11 @@ export default function Page({
deleteKeyCode={[]}
fitView={isEmptyFlow.current ? false : true}
fitViewOptions={{
minZoom: 0.4,
maxZoom: 1.25,
minZoom: 0.2,
maxZoom: 8,
}}
className="theme-attribution"
minZoom={0.4}
minZoom={0.2}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}

View file

@ -18,9 +18,10 @@ import { FlowSidebarComponent } from "./components/flowSidebarComponent";
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const types = useTypesStore((state) => state.types);
const { isFetched: typesLoaded } = useGetTypes({
useGetTypes({
enabled: Object.keys(types).length <= 0,
});
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
const currentFlow = useFlowStore((state) => state.currentFlow);
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);

View file

@ -28,13 +28,17 @@ export const EmptyPage = ({ setOpenModal }: EmptyPageProps) => {
>
{folders?.length > 1 ? "Empty folder" : "Start building"}
</h3>
<p className="pb-5 text-sm text-secondary-foreground">
<p
data-testid="empty-folder-description"
className="pb-5 text-sm text-secondary-foreground"
>
Begin with a template, or start from scratch.
</p>
<Button
variant="default"
onClick={() => setOpenModal(true)}
id="new-project-btn"
data-testid="new_project_btn_empty_page"
>
<ForwardedIconComponent
name="Plus"

View file

@ -245,13 +245,17 @@ export type ResponseErrorTypeAPI = {
export type ResponseErrorDetailAPI = {
response: { data: { detail: string } };
};
export type useQueryFunctionType<T = undefined, R = any> = T extends undefined
export type useQueryFunctionType<
T = undefined,
R = any,
O = {},
> = T extends undefined
? (
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
options?: Omit<UseQueryOptions, "queryFn" | "queryKey"> & O,
) => UseQueryResult<R>
: (
params: T,
options?: Omit<UseQueryOptions, "queryFn" | "queryKey">,
options?: Omit<UseQueryOptions, "queryFn" | "queryKey"> & O,
) => UseQueryResult<R>;
export type QueryFunctionType = (

View file

@ -6,20 +6,36 @@ test(
"user should be able to publish a flow",
{ tag: ["@release", "@workspace", "@api"] },
async ({ page, context }) => {
test.skip(); //@TODO understand this behavior
await awaitBootstrapTest(page);
await page.waitForSelector('[data-testid="blank-flow"]', {
timeout: 3000,
timeout: 5000,
});
const flowId = page.url().split("/").pop();
let flowId = "";
let retries = 0;
const maxRetries = 3;
while (flowId.length === 0 && retries < maxRetries) {
const url = page.url();
flowId = url.split("/").pop() || "";
if (flowId.length === 0) {
console.log(
`Empty flowId detected (attempt ${retries + 1}/${maxRetries}), waiting and retrying...`,
);
await page.waitForTimeout(1000);
retries++;
}
}
expect(flowId).toBeDefined();
expect(flowId).not.toBeNull();
expect(flowId!.length).toBeGreaterThan(0);
expect(flowId.length).toBeGreaterThan(0);
await page.getByTestId("blank-flow").click();
await page.waitForSelector('[data-testid="sidebar-search-input"]', {
timeout: 3000,
timeout: 5000,
});
await page.getByTestId("sidebar-search-input").click();
@ -35,20 +51,35 @@ test(
await page.getByTestId("add-component-button-chat-input").click();
});
await page.waitForTimeout(2000);
await adjustScreenView(page);
await page.getByTestId("publish-button").click();
await page.waitForTimeout(3000);
await page.waitForSelector('[data-testid="shareable-playground"]', {
timeout: 3000,
timeout: 10000,
});
await expect(
page.waitForResponse(
(response) =>
response.url().includes(flowId!) && response.status() === 200,
),
).resolves.toBeTruthy();
try {
await page.waitForTimeout(2000);
await expect(page.getByTestId("publish-switch")).toBeVisible({
timeout: 10000,
});
} catch (error) {
console.error("Error waiting for publish operation:", error);
throw error;
}
await page.waitForTimeout(2000);
await page.getByTestId("publish-switch").click();
const pagePromise = context.waitForEvent("page");
await page.waitForTimeout(2000);
await page.getByTestId("shareable-playground").click();
const newPage = await pagePromise;
await newPage.waitForTimeout(3000);
@ -61,7 +92,6 @@ test(
await newPage.close();
await page.bringToFront();
// check if deactivate the publishworks
await page.waitForTimeout(500);
await page.getByTestId("publish-button").click();
await page.waitForTimeout(500);
@ -73,6 +103,7 @@ test(
});
await expect(page.getByTestId("rf__wrapper")).toBeVisible();
await page.goto(newUrl);
await page.waitForTimeout(2000);
try {
await expect(page.getByTestId("mainpage_title")).toBeVisible({
timeout: 10000,

View file

@ -9,7 +9,6 @@ withEventDeliveryModes(
"Simple Agent",
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
test.skip(
!process?.env?.OPENAI_API_KEY,
@ -26,19 +25,21 @@ withEventDeliveryModes(
await page.getByRole("heading", { name: "Simple Agent" }).first().click();
await initialGPTsetup(page);
await page.getByTestId("textarea_str_input_value").first().fill("Hello");
await page.getByTestId("button_run_chat output").last().click();
await page.waitForSelector("text=built successfully", {
timeout: 10000 * 60 * 3,
});
await page.getByTestId("playground-btn-flow-io").click();
await page.waitForSelector('[data-testid="button-send"]', {
timeout: 3000,
});
await page
.getByTestId("input-chat-playground")
.last()
.fill("Hello, tell me about Langflow.");
await page.getByTestId("button-send").last().click();
const stopButton = page.getByRole("button", { name: "Stop" });
await stopButton.waitFor({ state: "visible", timeout: 30000 });
if (await stopButton.isVisible()) {
await expect(stopButton).toBeHidden({ timeout: 120000 });
}
const textContents = await page.getByTestId("div-chat-message").innerText();
@ -46,6 +47,6 @@ withEventDeliveryModes(
expect(await page.getByTestId("duration-display").last().isVisible());
expect(await page.getByTestId("icon-check").nth(0).isVisible());
expect(await page.getByTestId("icon-Check").nth(0).isVisible());
expect(textContents.length).toBeGreaterThan(10);
expect(textContents.length).toBeGreaterThan(30);
},
);

View file

@ -5,12 +5,58 @@ import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { initialGPTsetup } from "../../utils/initialGPTsetup";
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
function getRandomSocialMediaQuery(): string {
const companies = [
"OpenAI",
"Microsoft",
"Google",
"Tesla",
"Netflix",
"Spotify",
"Adobe",
"Amazon",
"Meta",
"Apple",
];
const platforms = [
"TikTok",
"Instagram",
"Twitter",
"LinkedIn",
"YouTube",
"Facebook",
];
const contentTypes = [
"latest video",
"recent post",
"profile bio",
"latest update",
"recent activity",
];
const randomCompany = companies[Math.floor(Math.random() * companies.length)];
const randomPlatform =
platforms[Math.floor(Math.random() * platforms.length)];
const randomContent1 =
contentTypes[Math.floor(Math.random() * contentTypes.length)];
let randomContent2 =
contentTypes[Math.floor(Math.random() * contentTypes.length)];
// Make sure we don't get the same content type twice
while (randomContent1 === randomContent2) {
randomContent2 =
contentTypes[Math.floor(Math.random() * contentTypes.length)];
}
return `Find the ${randomPlatform} profile of the company ${randomCompany} using Google search, then show me the ${randomContent1} and their ${randomContent2}.`;
}
withEventDeliveryModes(
"Social Media Agent",
{ tag: ["@release", "@starter-projects"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
test.skip(
!process?.env?.APIFY_API_TOKEN,
"APIFY_API_TOKEN required to run this test",
@ -48,9 +94,7 @@ withEventDeliveryModes(
await page
.getByTestId("input-chat-playground")
.last()
.fill(
"Find the TikTok profile of the company OpenAI using Google search, then show me the profile bio and their latest video.",
);
.fill(getRandomSocialMediaQuery());
await page.getByTestId("button-send").last().click();
@ -65,7 +109,7 @@ withEventDeliveryModes(
.getByTestId("div-chat-message")
.last()
.innerText();
expect(output).toContain("TikTok");
expect(output.length).toBeGreaterThan(150);
expect(output.length).toBeGreaterThan(100);
},
);

View file

@ -26,13 +26,18 @@ withEventDeliveryModes(
.click();
await initialGPTsetup(page);
await page.getByTestId("input-file-component").last().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("button_upload_file").click();
await page.getByTestId("drag-files-component").last().click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(__dirname, "../../assets/test_file.txt"),
);
await page.getByText("test_file.txt").isVisible();
await page.getByText("test_file.txt").last().isVisible();
await page.waitForTimeout(500);
await page.getByTestId("select-files-modal-button").click();
await page.waitForSelector('[data-testid="button_run_chat output"]', {
timeout: 3000,

View file

@ -2,13 +2,17 @@ import { expect, test } from "@playwright/test";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { extractAndCleanCode } from "../../utils/extract-and-clean-code";
import { initialGPTsetup } from "../../utils/initialGPTsetup";
import { withEventDeliveryModes } from "../../utils/withEventDeliveryModes";
// Add this line to declare Node.js global variables
declare const process: any;
declare const __dirname: string;
withEventDeliveryModes(
"Vector Store RAG",
{ tag: ["@release", "@starter-projects"] },
{ tag: ["@release", "@starter-projects", "@development"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
test.skip(
!process?.env?.OPENAI_API_KEY,
"OPENAI_API_KEY required to run this test",
@ -26,24 +30,13 @@ withEventDeliveryModes(
.first()
.click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
timeout: 20000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
let outdatedComponents = await page
.getByTestId("icon-AlertTriangle")
.count();
while (outdatedComponents > 0) {
await page.getByTestId("icon-AlertTriangle").first().click();
outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
}
let filledApiKey = await page.getByTestId("remove-icon-badge").count();
while (filledApiKey > 0) {
await page.getByTestId("remove-icon-badge").first().click();
filledApiKey = await page.getByTestId("remove-icon-badge").count();
}
await page.getByTestId("fit_view").click();
await initialGPTsetup(page);
if (process?.env?.ASTRA_DB_API_ENDPOINT?.includes("astra-dev")) {
await page.getByTestId("title-Astra DB").first().click();
await page.getByTestId("code-button-modal").click();
@ -72,85 +65,206 @@ withEventDeliveryModes(
await page.locator("textarea").last().fill(cleanCode);
await page.locator('//*[@id="checkAndSaveBtn"]').click();
}
const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
const isApiKeyInputVisible = await apiKeyInput.isVisible();
if (isApiKeyInputVisible) {
await apiKeyInput.fill(process.env.OPENAI_API_KEY ?? "");
}
await page
.getByTestId("popover-anchor-input-api_key") // input ID without "anchor-"
.nth(0)
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("popover-anchor-input-openai_api_key")
.nth(1)
.fill(process.env.OPENAI_API_KEY ?? "");
await page
.getByTestId("popover-anchor-input-openai_api_key")
.nth(0)
.fill(process.env.OPENAI_API_KEY ?? "");
await page.waitForSelector('[data-testid="title-Astra DB"]', {
timeout: 3000,
});
await page.waitForTimeout(500);
await page.getByTestId("fit_view").click();
// Astra DB tokens
await page
.getByTestId("popover-anchor-input-token")
.nth(0)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.waitFor({
timeout: 15000,
state: "visible",
});
let databaseDropdownCount = await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.count();
while (databaseDropdownCount === 0) {
await page
.getByTestId("popover-anchor-input-token")
.nth(0)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page.waitForTimeout(2000);
await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.waitFor({
timeout: 15000,
state: "visible",
});
databaseDropdownCount = await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.count();
}
await page.waitForTimeout(2000);
await page.getByTestId("dropdown_str_database_name").nth(0).click();
await page.waitForTimeout(2000);
let langflowCount = await page
.locator('[data-testid="langflow-0-option"]')
.count();
while (langflowCount === 0) {
await page.waitForTimeout(1000);
await page.getByTestId("icon-RefreshCcw").click();
await page.getByTestId("dropdown_str_database_name").nth(0).click();
await page.waitForTimeout(1000);
langflowCount = await page
.locator('[data-testid="langflow-0-option"]')
.count();
}
await page.locator('[data-testid="langflow-0-option"]').nth(0).waitFor({
timeout: 15000,
state: "visible",
});
await page.getByTestId("langflow-0-option").nth(0).click();
await page
.locator('[data-testid="dropdown_str_collection_name"]')
.nth(0)
.waitFor({
timeout: 15000,
state: "visible",
});
await page.waitForTimeout(2000);
await page.getByTestId("dropdown_str_collection_name").nth(0).click();
await page.locator('[data-testid="fe_tests-0-option"]').nth(0).waitFor({
timeout: 15000,
state: "visible",
});
await page.getByTestId("fe_tests-0-option").nth(0).click();
await page
.getByTestId("popover-anchor-input-token")
.nth(1)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page
.getByTestId("popover-anchor-input-collection_name_new")
.first()
.fill("fe_tests");
.locator('[data-testid="dropdown_str_database_name"]')
.nth(1)
.waitFor({
timeout: 15000,
state: "visible",
});
databaseDropdownCount = await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.count();
while (databaseDropdownCount === 0) {
await page
.getByTestId("popover-anchor-input-token")
.nth(0)
.fill(process.env.ASTRA_DB_APPLICATION_TOKEN ?? "");
await page.waitForTimeout(2000);
await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(1)
.waitFor({
timeout: 15000,
state: "visible",
});
databaseDropdownCount = await page
.locator('[data-testid="dropdown_str_database_name"]')
.nth(0)
.count();
}
await page.getByTestId("dropdown_str_database_name").nth(1).click();
await page.waitForTimeout(2000);
langflowCount = await page
.locator('[data-testid="langflow-0-option"]')
.count();
while (langflowCount === 0) {
await page.waitForTimeout(1000);
await page.getByTestId("icon-RefreshCcw").click();
const loadingOptions = page.getByText("Loading options...");
await loadingOptions.waitFor({ state: "visible", timeout: 30000 });
if (await loadingOptions.isVisible()) {
await expect(loadingOptions).toBeHidden({ timeout: 120000 });
}
await page.getByTestId("dropdown_str_database_name").nth(1).click();
await page.waitForTimeout(1000);
langflowCount = await page
.locator('[data-testid="langflow-0-option"]')
.count();
}
await page.getByTestId("langflow-0-option").nth(0).click();
await page.waitForTimeout(2000);
await page
.getByTestId("popover-anchor-input-collection_name_new")
.last()
.fill("fe_tests");
.locator('[data-testid="dropdown_str_collection_name"]')
.nth(1)
.waitFor({
timeout: 15000 * 3,
state: "visible",
});
await page.waitForTimeout(500);
await page.getByTestId("dropdown_str_collection_name").nth(1).click();
// Click first refresh button and wait for disabled->enabled transition
await page.getByTestId("refresh-button-api_endpoint").first().click();
await expect(
page.getByTestId("refresh-button-api_endpoint").first(),
).toHaveAttribute("disabled", "");
await expect(
page.getByTestId("refresh-button-api_endpoint").first(),
).not.toHaveAttribute("disabled", "");
await page.waitForTimeout(2000);
// Click second refresh button and wait for disabled->enabled transition
await page.getByTestId("refresh-button-api_endpoint").last().click();
await expect(
page.getByTestId("refresh-button-api_endpoint").last(),
).toHaveAttribute("disabled", "");
await expect(
page.getByTestId("refresh-button-api_endpoint").last(),
).not.toHaveAttribute("disabled", "");
await page.getByTestId("dropdown_str_api_endpoint").first().click();
await page.waitForSelector('[data-testid="langflow-1-option"]', {
timeout: 100000,
await page.locator('[data-testid="fe_tests-0-option"]').nth(0).waitFor({
timeout: 15000,
state: "visible",
});
await page.getByTestId("langflow-1-option").last().click();
await page.getByTestId("fe_tests-0-option").nth(0).click();
await page.getByTestId("dropdown_str_api_endpoint").last().click();
await page.waitForSelector('[data-testid="langflow-1-option"]', {
timeout: 100000,
});
await page.getByTestId("langflow-1-option").last().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("input-file-component").last().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("drag-files-component").last().click();
const fileChooser = await fileChooserPromise;
await fileChooser.setFiles(
path.join(__dirname, "../../assets/test_file.txt"),
);
await page.getByText("test_file.txt").isVisible();
await page.getByText("test_file.txt").last().isVisible();
await page.waitForTimeout(500);
await page.getByTestId("select-files-modal-button").click();
await page.getByTestId("button_run_astra db").last().click();
await page.waitForSelector("text=built successfully", {
timeout: 60000 * 2,
@ -168,7 +282,7 @@ withEventDeliveryModes(
await page.getByText("Playground", { exact: true }).last().click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
timeout: 60000,
});
await page.getByTestId("input-chat-playground").last().fill("hello");
await page.getByTestId("input-chat-playground").last().click();
@ -192,8 +306,9 @@ withEventDeliveryModes(
await page.getByRole("combobox").click();
await page.getByLabel("Delete").click();
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
timeout: 60000,
});
await page.getByTestId("input-chat-playground").last().isVisible();
},
{ timeout: 60000 },
);

View file

@ -2,7 +2,6 @@ import { expect, test } from "@playwright/test";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
test.skip(); //@TODO understand this behavior
await awaitBootstrapTest(page);
await page.waitForSelector('[data-testid="blank-flow"]', {
@ -54,9 +53,7 @@ test("chat_io_teste", { tag: ["@release", "@workspace"] }, async ({ page }) => {
await page.getByTestId("input-chat-playground").click();
await page.getByTestId("input-chat-playground").fill("teste");
await page.getByTestId("button-send").first().click();
const chat_input = await page
.getByTestId("chat-message-User-teste")
.textContent();
const chat_input = await page.getByTestId("div-chat-message").textContent();
expect(chat_input).toBe("teste");
});

View file

@ -47,6 +47,8 @@ test(
await page.getByTestId("dropdown_str_model_id").click();
await page.getByText("anthropic.claude-v2").last().click();
await page.waitForTimeout(1000);
value = await page.getByTestId("dropdown_str_model_id").innerText();
if (value !== "anthropic.claude-v2:1") {
expect(false).toBeTruthy();
@ -59,6 +61,8 @@ test(
await page.getByTestId("more-options-modal").click();
await page.getByTestId("advanced-button-modal").click();
await page.waitForTimeout(1000);
value = await page
.getByTestId("value-dropdown-dropdown_str_edit_model_id")
.innerText();

View file

@ -22,9 +22,18 @@ test(
.getByTestId("modelsNVIDIA")
.hover()
.then(async () => {
// Wait for the API request to complete after clicking the add button
const responsePromise = page.waitForResponse(
(response) =>
response.url().includes("/api/v1/custom_component/update") &&
response.status() === 200,
);
await page.getByTestId("add-component-button-nvidia").click();
await responsePromise; // Wait for the request to complete
});
//add
await page.getByTestId("title-NVIDIA").click();
await page.getByTestId("edit-button-modal").click();

View file

@ -1,21 +1,26 @@
import { expect, test } from "@playwright/test";
import fs from "fs";
import path from "path";
import { addFlowToTestOnEmptyLangflow } from "../../utils/add-flow-to-test-on-empty-langflow";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
import { generateRandomFilename } from "../../utils/generate-filename";
// Function to generate random 10-character filename
// Configure tests to run serially with a delay between each test
test(
"should navigate to files page and show empty state",
{ tag: ["@release", "@files"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
await awaitBootstrapTest(page, { skipModal: true });
// Wait for the sidebar to be visible
await page.waitForSelector('[data-testid="folder-sidebar"]', {
const firstRunLangflow = await page
.getByTestId("empty-folder-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
@ -46,18 +51,24 @@ test(
"should upload file using upload button",
{ tag: ["@release", "@files"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
const fileName = generateRandomFilename();
const testFilePath = path.join(__dirname, "../../assets/test-file.txt");
const fileContent = fs.readFileSync(testFilePath);
await awaitBootstrapTest(page, { skipModal: true });
// Navigate to files page
await page.waitForSelector('[data-testid="folder-sidebar"]', {
const firstRunLangflow = await page
.getByTestId("empty-folder-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByText("My Files").first().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("upload-file-btn").click();
@ -85,16 +96,22 @@ test(
"should upload file using drag and drop",
{ tag: ["@release", "@files"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
const fileName = generateRandomFilename();
await awaitBootstrapTest(page, { skipModal: true });
// Navigate to files page
await page.waitForSelector('[data-testid="folder-sidebar"]', {
const firstRunLangflow = await page
.getByTestId("empty-folder-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByText("My Files").first().click();
// Create DataTransfer object and file
@ -135,8 +152,6 @@ test(
"should upload multiple files with different types",
{ tag: ["@release", "@files"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
const fileNames = {
txt: generateRandomFilename(),
json: generateRandomFilename(),
@ -153,10 +168,18 @@ test(
await awaitBootstrapTest(page, { skipModal: true });
// Navigate to files page
await page.waitForSelector('[data-testid="folder-sidebar"]', {
const firstRunLangflow = await page
.getByTestId("empty-folder-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByText("My Files").first().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("upload-file-btn").click();
@ -201,8 +224,6 @@ test(
"should search uploaded files",
{ tag: ["@release", "@files"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
const fileNames = {
txt: generateRandomFilename(),
json: generateRandomFilename(),
@ -219,10 +240,18 @@ test(
await awaitBootstrapTest(page, { skipModal: true });
// Navigate to files page
await page.waitForSelector('[data-testid="folder-sidebar"]', {
const firstRunLangflow = await page
.getByTestId("empty-folder-description")
.count();
if (firstRunLangflow > 0) {
await addFlowToTestOnEmptyLangflow(page);
}
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
await page.getByText("My Files").first().click();
const fileChooserPromise = page.waitForEvent("filechooser");
await page.getByTestId("upload-file-btn").click();

View file

@ -1,5 +1,13 @@
import { expect, test } from "@playwright/test";
test.beforeAll(async () => {
await new Promise((resolve) => setTimeout(resolve, 7000));
});
test.afterEach(async () => {
await new Promise((resolve) => setTimeout(resolve, 7000));
});
test(
"should see general profile gradient",
{ tag: ["@release"] },
@ -29,7 +37,6 @@ test(
{ tag: ["@release", "@workspace", "@api"] },
async ({ page }) => {
test.skip(); //@TODO understand this behavior
const randomName = Math.random().toString(36).substring(2);
const randomName2 = Math.random().toString(36).substring(2);
const randomName3 = Math.random().toString(36).substring(2);

View file

@ -0,0 +1,8 @@
import { Page } from "playwright/test";
export const addFlowToTestOnEmptyLangflow = async (page: Page) => {
await page.getByTestId("new_project_btn_empty_page").click();
await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.getByTestId("icon-ChevronLeft").click();
};

View file

@ -2,11 +2,22 @@ import { Page } from "playwright/test";
export async function addNewApiKeys(page: Page) {
const apiKeyInput = page.getByTestId("popover-anchor-input-api_key");
const openaiApiKeyInput = page.getByTestId(
"popover-anchor-input-openai_api_key",
);
const isApiKeyInputVisible = await apiKeyInput.count();
const isOpenaiApiKeyInputVisible = await openaiApiKeyInput.count();
if (isApiKeyInputVisible > 0) {
for (let i = 0; i < isApiKeyInputVisible; i++) {
await apiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? "");
}
}
if (isOpenaiApiKeyInputVisible > 0) {
for (let i = 0; i < isOpenaiApiKeyInputVisible; i++) {
await openaiApiKeyInput.nth(i).fill(process.env.OPENAI_API_KEY ?? "");
}
}
}

View file

@ -3,7 +3,11 @@ import { Page } from "playwright/test";
export async function removeOldApiKeys(page: Page) {
let filledApiKey = await page.getByTestId("remove-icon-badge").count();
while (filledApiKey > 0) {
await page.getByTestId("remove-icon-badge").first().click();
await page
.getByTestId("remove-icon-badge")
.nth(filledApiKey - 1)
.click();
await page.waitForTimeout(1000);
filledApiKey = await page.getByTestId("remove-icon-badge").count();
}
}

View file

@ -32,6 +32,7 @@ function getMimeType(extension: string): string {
}
export async function uploadFile(page: Page, fileName: string) {
await page.getByTestId("fit_view").click();
const fileManagement = await page
.getByTestId("button_open_file_management")
?.isVisible();

View file

@ -5,6 +5,7 @@ type TestConfig = Parameters<typeof test>[1];
/**
* Wraps a test function to run it with both streaming and polling event delivery modes.
* Adds a 3-second delay between test runs to ensure proper separation.
*
* @param title The test title
* @param config The test configuration (tags, etc)
@ -14,11 +15,16 @@ export function withEventDeliveryModes(
title: string,
config: TestConfig,
testFn: TestFunction,
{ timeout = 10000 }: { timeout?: number } = {},
) {
const eventDeliveryModes = ["streaming", "polling"] as const;
for (const eventDelivery of eventDeliveryModes) {
for (const [index, eventDelivery] of eventDeliveryModes.entries()) {
test(`${title} - ${eventDelivery}`, config, async ({ page }) => {
if (index === 0) {
await new Promise((resolve) => setTimeout(resolve, timeout));
}
// Intercept the config request and modify the event_delivery setting
await page.route("**/api/v1/config", async (route) => {
const response = await route.fetch();