Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/frontend/tests/core/features/tweaksTest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ test(

await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Basic Prompting" }).click();
// Wait for the new-flow Loading state to clear before checking the
// publish button — the canvas mounts only after the flow finishes
// loading, which can outlast a 20s action timeout on Windows CI.
await page.waitForSelector('text="Loading"', {
state: "hidden",
timeout: 60000,
});
await page.waitForSelector('[data-testid="publish-button"]', {
timeout: 30000,
});
await page.getByTestId("publish-button").click();
await page.getByTestId("api-access-item").click();
await page.getByTestId("api_tab_curl").click();
Expand Down
16 changes: 13 additions & 3 deletions src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ test(
});
await page.getByRole("button", { name: "Sign In" }).click();

// Create User A
// Create User A — wait for the homepage Loading state to clear before
// checking mainpage_title (mainpage_title only renders after data load,
// which can outlast a 30s wait on slower runners like Windows CI).
await page.waitForSelector('text="Loading"', {
state: "hidden",
timeout: 60000,
});
await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});
Expand Down Expand Up @@ -106,11 +112,15 @@ test(
}

await page.waitForSelector('[data-testid="modal-title"]', {
timeout: 3000,
timeout: 30000,
});
await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Basic Prompting" }).click();
// Match the canvas_controls_dropdown wait used elsewhere in the suite
// (tests/core/features/token-usage.spec.ts, chatInputOutput.spec.ts):
// the new-flow canvas can take well over 30s to mount on slower runners.
await page.waitForSelector('[data-testid="canvas_controls_dropdown"]', {
timeout: 30000,
timeout: 100000,
});

await renameFlow(page, { flowName: userAFlowName });
Expand Down
8 changes: 4 additions & 4 deletions src/frontend/tests/core/unit/customEdgesComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("search api");
await page.waitForSelector('[data-testid="searchapiSearchApi"]', {
timeout: 1000,
timeout: 10000,
});

await zoomOut(page, 3);
Expand All @@ -32,7 +32,7 @@ test(
await page.waitForSelector(
'[data-testid="langchain_utilitiesTool Calling Agent"]',
{
timeout: 1000,
timeout: 10000,
},
);

Expand Down Expand Up @@ -75,7 +75,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("search api");
await page.waitForSelector('[data-testid="searchapiSearchApi"]', {
timeout: 1000,
timeout: 10000,
});

await zoomOut(page, 3);
Expand All @@ -91,7 +91,7 @@ test(
await page.waitForSelector(
'[data-testid="langchain_utilitiesTool Calling Agent"]',
{
timeout: 1000,
timeout: 10000,
},
);

Expand Down
10 changes: 5 additions & 5 deletions src/frontend/tests/core/unit/fileUploadComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ test(
},
);
await expect(page.getByText(`${newTxtFile}.txt`).first()).toBeVisible({
timeout: 10000,
timeout: 30000,
});
await page.getByTestId(`remove-file-button-${renamedTxtFile}`).click();

Expand Down Expand Up @@ -826,7 +826,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Read File");
await page.waitForSelector('[data-testid="files_and_knowledgeRead File"]', {
timeout: 1000,
timeout: 10000,
});

// Get flow ID from URL
Expand Down Expand Up @@ -902,7 +902,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Text Input");
await page.waitForSelector('[data-testid="input_outputText Input"]', {
timeout: 1000,
timeout: 10000,
});

await adjustScreenView(page, { numberOfZoomOut: 3 });
Expand Down Expand Up @@ -932,7 +932,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Text Input");
await page.waitForSelector('[data-testid="input_outputText Input"]', {
timeout: 1000,
timeout: 10000,
});

await adjustScreenView(page, { numberOfZoomOut: 3 });
Expand All @@ -958,7 +958,7 @@ test(
await page.getByTestId("sidebar-search-input").click();
await page.getByTestId("sidebar-search-input").fill("Chat Output");
await page.waitForSelector('[data-testid="input_outputChat Output"]', {
timeout: 1000,
timeout: 10000,
});
await page
.getByTestId("input_outputChat Output")
Expand Down
20 changes: 20 additions & 0 deletions src/frontend/tests/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,29 @@
import { test as base, expect, Page } from "@playwright/test";
import "./playwrightCoverage";

// Optional CPU throttling for reproducing race conditions seen on slower
// runners (Windows CI). Enable with LF_CPU_THROTTLE=<rate>, e.g. 4.
const CPU_THROTTLE_RATE = (() => {
const raw = process.env.LF_CPU_THROTTLE;
if (!raw) return 0;
const n = Number.parseFloat(raw);
return Number.isFinite(n) && n > 1 ? n : 0;
})();

// Extend test to log backend errors
export const test = base.extend({
page: async ({ page }, use) => {
if (CPU_THROTTLE_RATE > 0) {
try {
const client = await page.context().newCDPSession(page);
await client.send("Emulation.setCPUThrottlingRate", {
rate: CPU_THROTTLE_RATE,
});
} catch {
// Throttling is best-effort and only supported on Chromium.
}
}

const errors: Array<{
url: string;
status: number;
Expand Down
90 changes: 68 additions & 22 deletions src/frontend/tests/globalTeardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,77 @@
import fs from "fs";
import path from "path";

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

// On Windows, the uvicorn process can still hold SQLite file handles when
// teardown runs. POSIX allows unlinking files with open handles; Win32 does
// not, surfacing as EBUSY/EPERM. Retry with backoff, fall back to walking the
// tree and removing children individually, and never throw out of teardown.
async function removeWithRetry(target: string): Promise<boolean> {
const attempts = 5;
for (let i = 0; i < attempts; i++) {
try {
fs.rmSync(target, { recursive: true, force: true });
if (!fs.existsSync(target)) return true;
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code !== "EBUSY" && code !== "EPERM" && code !== "ENOTEMPTY") {
throw err;
}
}
await sleep(200 * 2 ** i);
}
return !fs.existsSync(target);
}

function removeChildrenBestEffort(target: string): string[] {
const failed: string[] = [];
let entries: fs.Dirent[];
try {
entries = fs.readdirSync(target, { withFileTypes: true });
} catch {
return failed;
}
for (const entry of entries) {
const childPath = path.join(target, entry.name);
try {
fs.rmSync(childPath, { recursive: true, force: true });
} catch {
failed.push(childPath);
}
}
return failed;
}

export default async () => {
console.warn("Removing the temp database");
// this file is in src/frontend/tests/globalTeardown.ts
// temp is in src/frontend/temp
const tempDbPath = path.join(__dirname, "..", "temp");
console.warn("tempDbPath", tempDbPath);

if (!fs.existsSync(tempDbPath)) {
console.warn("Temp database directory does not exist, skipping removal");
return;
}

try {
console.warn("Removing the temp database");
// Check if the file exists in the path
// this file is in src/frontend/tests/globalTeardown.ts
// temp is in src/frontend/temp
const tempDbPath = path.join(__dirname, "..", "temp");
console.warn("tempDbPath", tempDbPath);

// Check if the directory exists before attempting to remove it
if (fs.existsSync(tempDbPath)) {
// Remove the temp database
fs.rmSync(tempDbPath, { recursive: true, force: true });

// Check if the file is removed
if (!fs.existsSync(tempDbPath)) {
console.warn("Successfully removed the temp database");
} else {
console.error(
"Error: temp database still exists after removal attempt",
);
}
} else {
console.warn("Temp database directory does not exist, skipping removal");
if (await removeWithRetry(tempDbPath)) {
console.warn("Successfully removed the temp database");
return;
}

const stragglers = removeChildrenBestEffort(tempDbPath);
if (await removeWithRetry(tempDbPath)) {
console.warn(
"Successfully removed the temp database after per-file fallback",
);
return;
}

console.warn(
`Temp database directory still present after retries; leaving it for the runner workspace cleanup. Files that resisted removal: ${stragglers.length}`,
);
} catch (error) {
console.error("Error while removing the temp database:", error);
}
Expand Down
13 changes: 8 additions & 5 deletions src/frontend/tests/utils/add-new-user-and-loggin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ export const addNewUserAndLogin = async (page: Page) => {

await page.getByRole("button", { name: "Sign In" }).click();

await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});

// Wait for any loading text to disappear
// Wait for any loading text to disappear before checking the homepage:
// mainpage_title only renders after the homepage data finishes loading,
// and on slower runners (Windows CI) the Loading state can outlast a
// 30s mainpage_title wait.
await page.waitForSelector('text="Loading"', {
state: "hidden",
timeout: 60000,
});

await page.waitForSelector('[data-testid="mainpage_title"]', {
timeout: 30000,
});

Expand Down
Loading