mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-25 11:10:43 +08:00
fix: Correct read_project pagination logic and improve test stability (nightly fix) (#10076)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a7ecf04579
commit
e61316fe95
@ -7,7 +7,7 @@ from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
import orjson
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, Response, UploadFile, status
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, Query, Response, UploadFile, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from fastapi_pagination import Params
|
||||
@ -228,6 +228,8 @@ async def read_project(
|
||||
project_id: UUID,
|
||||
current_user: CurrentActiveUser,
|
||||
params: Annotated[Params | None, Depends(custom_params)],
|
||||
page: Annotated[int | None, Query()] = None,
|
||||
size: Annotated[int | None, Query()] = None,
|
||||
is_component: bool = False,
|
||||
is_flow: bool = False,
|
||||
search: str = "",
|
||||
@ -249,7 +251,8 @@ async def read_project(
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
|
||||
try:
|
||||
if params and params.page and params.size:
|
||||
# Check if pagination is explicitly requested by the user (both page and size provided)
|
||||
if page is not None and size is not None:
|
||||
stmt = select(Flow).where(Flow.folder_id == project_id)
|
||||
|
||||
if Flow.updated_at is not None:
|
||||
@ -260,6 +263,7 @@ async def read_project(
|
||||
stmt = stmt.where(Flow.is_component == False) # noqa: E712
|
||||
if search:
|
||||
stmt = stmt.where(Flow.name.like(f"%{search}%")) # type: ignore[attr-defined]
|
||||
|
||||
import warnings
|
||||
|
||||
with warnings.catch_warnings():
|
||||
@ -270,13 +274,14 @@ async def read_project(
|
||||
|
||||
return FolderWithPaginatedFlows(folder=FolderRead.model_validate(project), flows=paginated_flows)
|
||||
|
||||
# If no pagination requested, return all flows for the current user
|
||||
flows_from_current_user_in_project = [flow for flow in project.flows if flow.user_id == current_user.id]
|
||||
project.flows = flows_from_current_user_in_project
|
||||
return project # noqa: TRY300
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
|
||||
flows_from_current_user_in_project = [flow for flow in project.flows if flow.user_id == current_user.id]
|
||||
project.flows = flows_from_current_user_in_project
|
||||
return project
|
||||
|
||||
|
||||
@router.patch("/{project_id}", response_model=FolderRead, status_code=200)
|
||||
async def update_project(
|
||||
|
||||
@ -476,7 +476,7 @@ class TestProjectMCPIntegration:
|
||||
|
||||
# Mock API key creation
|
||||
mock_api_key_response = MagicMock()
|
||||
mock_api_key_response.api_key = "test-api-key-123"
|
||||
mock_api_key_response.api_key = "test-api-key-123" # pragma: allowlist secret
|
||||
mock_create_api_key.return_value = mock_api_key_response
|
||||
|
||||
# Mock validation - no conflict
|
||||
@ -885,3 +885,214 @@ class TestProjectMCPIntegration:
|
||||
result = response.json()
|
||||
assert "name" in result
|
||||
assert result["name"] == basic_case["name"]
|
||||
|
||||
|
||||
# Tests for the read_project bug fix
|
||||
class TestReadProjectBugFix:
|
||||
"""Test the read_project endpoint fix for ASGI response bug."""
|
||||
|
||||
async def test_read_project_without_pagination_params(self, client: AsyncClient, logged_in_headers, basic_case):
|
||||
"""Test read_project returns correct response when no pagination params are provided."""
|
||||
# Create a project first
|
||||
create_response = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
project_id = create_response.json()["id"]
|
||||
|
||||
# Read project without pagination params
|
||||
response = await client.get(f"api/v1/projects/{project_id}", headers=logged_in_headers)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return FolderReadWithFlows (direct project response)
|
||||
assert isinstance(result, dict)
|
||||
assert "name" in result
|
||||
assert "description" in result
|
||||
assert "id" in result
|
||||
assert "flows" in result
|
||||
assert result["name"] == basic_case["name"]
|
||||
|
||||
async def test_read_project_with_pagination_params(self, client: AsyncClient, logged_in_headers, basic_case):
|
||||
"""Test read_project returns paginated response when pagination params are provided."""
|
||||
# Create a project first
|
||||
create_response = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
project_id = create_response.json()["id"]
|
||||
|
||||
# Read project with pagination params
|
||||
response = await client.get(f"api/v1/projects/{project_id}?page=1&size=10", headers=logged_in_headers)
|
||||
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return FolderWithPaginatedFlows (paginated response)
|
||||
assert isinstance(result, dict)
|
||||
assert "folder" in result
|
||||
assert "flows" in result
|
||||
|
||||
# Check folder structure
|
||||
folder = result["folder"]
|
||||
assert "name" in folder
|
||||
assert "description" in folder
|
||||
assert "id" in folder
|
||||
assert folder["name"] == basic_case["name"]
|
||||
|
||||
# Check flows pagination structure
|
||||
flows = result["flows"]
|
||||
assert "items" in flows
|
||||
assert "total" in flows
|
||||
assert "page" in flows
|
||||
assert "size" in flows
|
||||
|
||||
async def test_read_project_with_partial_pagination_params(
|
||||
self, client: AsyncClient, logged_in_headers, basic_case
|
||||
):
|
||||
"""Test read_project behavior when only some pagination params are provided."""
|
||||
# Create a project first
|
||||
create_response = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
project_id = create_response.json()["id"]
|
||||
|
||||
# Test with only page param (no size)
|
||||
response = await client.get(f"api/v1/projects/{project_id}?page=1", headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return non-paginated response (FolderReadWithFlows)
|
||||
assert isinstance(result, dict)
|
||||
assert "name" in result # Direct project response
|
||||
assert "flows" in result
|
||||
assert result["name"] == basic_case["name"]
|
||||
|
||||
# Test with only size param (no page)
|
||||
response = await client.get(f"api/v1/projects/{project_id}?size=10", headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return non-paginated response (FolderReadWithFlows)
|
||||
assert isinstance(result, dict)
|
||||
assert "name" in result # Direct project response
|
||||
assert "flows" in result
|
||||
assert result["name"] == basic_case["name"]
|
||||
|
||||
async def test_read_project_with_filtering_params(self, client: AsyncClient, logged_in_headers, basic_case):
|
||||
"""Test read_project with filtering parameters (is_component, is_flow, search)."""
|
||||
# Create a project first
|
||||
create_response = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
project_id = create_response.json()["id"]
|
||||
|
||||
# Create a flow and component in the project for filtering tests
|
||||
flow_payload = {
|
||||
"name": "Test Flow",
|
||||
"description": "A test flow",
|
||||
"folder_id": project_id,
|
||||
"data": {"nodes": [], "edges": []},
|
||||
"is_component": False,
|
||||
}
|
||||
component_payload = {
|
||||
"name": "Test Component",
|
||||
"description": "A test component",
|
||||
"folder_id": project_id,
|
||||
"data": {"nodes": [], "edges": []},
|
||||
"is_component": True,
|
||||
}
|
||||
|
||||
flow_response = await client.post("api/v1/flows/", json=flow_payload, headers=logged_in_headers)
|
||||
comp_response = await client.post("api/v1/flows/", json=component_payload, headers=logged_in_headers)
|
||||
assert flow_response.status_code == status.HTTP_201_CREATED
|
||||
assert comp_response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
# Test with filtering params but no pagination (should use non-paginated path)
|
||||
response = await client.get(f"api/v1/projects/{project_id}?is_flow=true", headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return non-paginated response
|
||||
assert isinstance(result, dict)
|
||||
assert "name" in result
|
||||
assert "flows" in result
|
||||
|
||||
# Test with filtering params AND pagination (should use paginated path)
|
||||
response = await client.get(
|
||||
f"api/v1/projects/{project_id}?is_flow=true&page=1&size=10", headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
result = response.json()
|
||||
|
||||
# Should return paginated response
|
||||
assert isinstance(result, dict)
|
||||
assert "folder" in result
|
||||
assert "flows" in result
|
||||
assert "items" in result["flows"]
|
||||
|
||||
async def test_read_project_consistent_response_structure(self, client: AsyncClient, logged_in_headers, basic_case):
|
||||
"""Test that read_project returns consistent response structure in all cases."""
|
||||
# Create a project first
|
||||
create_response = await client.post("api/v1/projects/", json=basic_case, headers=logged_in_headers)
|
||||
assert create_response.status_code == status.HTTP_201_CREATED
|
||||
project_id = create_response.json()["id"]
|
||||
|
||||
# Test multiple request scenarios to ensure consistency
|
||||
test_cases = [
|
||||
# No params - should return FolderReadWithFlows
|
||||
{"params": "", "expect_paginated": False},
|
||||
# Only search - should return FolderReadWithFlows
|
||||
{"params": "?search=test", "expect_paginated": False},
|
||||
# Only is_component - should return FolderReadWithFlows
|
||||
{"params": "?is_component=true", "expect_paginated": False},
|
||||
# Only is_flow - should return FolderReadWithFlows
|
||||
{"params": "?is_flow=true", "expect_paginated": False},
|
||||
# Only page - should return FolderReadWithFlows
|
||||
{"params": "?page=1", "expect_paginated": False},
|
||||
# Only size - should return FolderReadWithFlows
|
||||
{"params": "?size=10", "expect_paginated": False},
|
||||
# Both page and size - should return FolderWithPaginatedFlows
|
||||
{"params": "?page=1&size=10", "expect_paginated": True},
|
||||
# Page, size and filters - should return FolderWithPaginatedFlows
|
||||
{"params": "?page=1&size=10&is_flow=true", "expect_paginated": True},
|
||||
]
|
||||
|
||||
for test_case in test_cases:
|
||||
response = await client.get(f"api/v1/projects/{project_id}{test_case['params']}", headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_200_OK, f"Failed for params: {test_case['params']}"
|
||||
|
||||
result = response.json()
|
||||
assert isinstance(result, dict), f"Result should be dict for params: {test_case['params']}"
|
||||
|
||||
if test_case["expect_paginated"]:
|
||||
# Paginated response structure
|
||||
assert "folder" in result, f"Paginated response missing 'folder' for params: {test_case['params']}"
|
||||
assert "flows" in result, f"Paginated response missing 'flows' for params: {test_case['params']}"
|
||||
assert "items" in result["flows"], f"Paginated flows missing 'items' for params: {test_case['params']}"
|
||||
assert "total" in result["flows"], f"Paginated flows missing 'total' for params: {test_case['params']}"
|
||||
else:
|
||||
# Non-paginated response structure
|
||||
assert "name" in result, f"Non-paginated response missing 'name' for params: {test_case['params']}"
|
||||
assert "flows" in result, f"Non-paginated response missing 'flows' for params: {test_case['params']}"
|
||||
# Should NOT have pagination structure
|
||||
assert "folder" not in result, (
|
||||
f"Non-paginated response should not have 'folder' for params: {test_case['params']}"
|
||||
)
|
||||
|
||||
async def test_read_project_error_handling_consistency(self, client: AsyncClient, logged_in_headers):
|
||||
"""Test that error handling is consistent across both response paths."""
|
||||
import uuid
|
||||
|
||||
non_existent_id = str(uuid.uuid4())
|
||||
|
||||
# Test both pagination and non-pagination paths with non-existent project
|
||||
test_cases = [
|
||||
"", # Non-paginated path
|
||||
"?page=1&size=10", # Paginated path
|
||||
]
|
||||
|
||||
for params in test_cases:
|
||||
response = await client.get(f"api/v1/projects/{non_existent_id}{params}", headers=logged_in_headers)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND, f"Should return 404 for params: {params}"
|
||||
|
||||
result = response.json()
|
||||
assert "detail" in result, f"Error response should have 'detail' for params: {params}"
|
||||
assert "not found" in result["detail"].lower(), (
|
||||
f"Error message should mention 'not found' for params: {params}"
|
||||
)
|
||||
|
||||
@ -25,7 +25,7 @@ export default defineConfig({
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: 2,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
timeout: 3 * 60 * 750,
|
||||
timeout: 5 * 60 * 1000, // 5 minutes
|
||||
// reporter: [
|
||||
// ["html", { open: "never", outputFolder: "playwright-report/test-results" }],
|
||||
// ],
|
||||
@ -121,6 +121,7 @@ export default defineConfig({
|
||||
env: {
|
||||
VITE_PROXY_TARGET: "http://localhost:7860",
|
||||
},
|
||||
reuseExistingServer: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@ -1,6 +1,35 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
// Helper function to ensure element stability before interaction
|
||||
async function waitForElementStability(
|
||||
page: any,
|
||||
selector: string,
|
||||
timeout = 5000,
|
||||
) {
|
||||
await page.waitForSelector(selector, { state: "visible", timeout });
|
||||
// Additional wait to ensure element is stable and not moving
|
||||
await page.waitForTimeout(200);
|
||||
// Use first() to avoid strict mode violation when multiple elements exist
|
||||
await expect(page.locator(selector).first()).toBeVisible();
|
||||
}
|
||||
|
||||
// Helper function to safely navigate with proper waiting
|
||||
async function safeNavigateAndClick(
|
||||
page: any,
|
||||
testId: string,
|
||||
waitForSelector?: string,
|
||||
) {
|
||||
const element = page.getByTestId(testId);
|
||||
await expect(element).toBeVisible({ timeout: 15000 });
|
||||
await element.click();
|
||||
|
||||
if (waitForSelector) {
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await waitForElementStability(page, waitForSelector);
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"user should be able to use third quarter of starter projects without any outdated components on the flow",
|
||||
{ tag: ["@release", "@components"] },
|
||||
@ -10,12 +39,33 @@ test(
|
||||
const templatesData = [];
|
||||
let numberOfOutdatedComponents = 0;
|
||||
|
||||
// First, collect all template data
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
// First, collect all template data with proper waiting
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
|
||||
const numberOfTemplates = await page
|
||||
// Wait for templates to fully load and stabilize
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
// Ensure template count is stable by checking multiple times
|
||||
let numberOfTemplates = await page
|
||||
.getByTestId("text_card_container")
|
||||
.count();
|
||||
await page.waitForTimeout(1000); // Allow any dynamic loading to complete
|
||||
const verifyCount = await page.getByTestId("text_card_container").count();
|
||||
|
||||
// If counts don't match, wait a bit more and try again
|
||||
if (numberOfTemplates !== verifyCount) {
|
||||
await page.waitForTimeout(2000);
|
||||
numberOfTemplates = await page.getByTestId("text_card_container").count();
|
||||
}
|
||||
|
||||
const secondQuarterEnd = Math.ceil((numberOfTemplates * 2) / 4);
|
||||
const thirdQuarterEnd = Math.ceil((numberOfTemplates * 3) / 4);
|
||||
@ -24,13 +74,13 @@ test(
|
||||
`Total templates: ${numberOfTemplates}, Testing from ${secondQuarterEnd} to ${thirdQuarterEnd - 1} (third quarter)`,
|
||||
);
|
||||
|
||||
// Collect template names first
|
||||
// Collect template names first with stability checks
|
||||
for (let i = secondQuarterEnd; i < thirdQuarterEnd; i++) {
|
||||
const exampleName = await page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(i)
|
||||
.getAttribute("role");
|
||||
// Ensure the specific template card is visible and stable
|
||||
const templateCard = page.getByTestId("text_card_container").nth(i);
|
||||
await expect(templateCard).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const exampleName = await templateCard.getAttribute("role");
|
||||
templatesData.push({ index: i, name: exampleName });
|
||||
}
|
||||
|
||||
@ -43,29 +93,62 @@ test(
|
||||
for (const template of templatesData) {
|
||||
console.log(`Testing template ${template.index}: ${template.name}`);
|
||||
|
||||
// Navigate directly to templates page
|
||||
await page.goto("/");
|
||||
// Navigate directly to templates page with improved stability
|
||||
await page.goto("/", { waitUntil: "networkidle", timeout: 30000 });
|
||||
|
||||
// Wait for main page to be fully loaded and stable
|
||||
await expect(page.getByTestId("mainpage_title")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("new-project-btn").first().click();
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Wait for templates to load
|
||||
await page.waitForSelector('[data-testid="text_card_container"]', {
|
||||
timeout: 10000,
|
||||
});
|
||||
// Sequential navigation with proper waiting (no Promise.all)
|
||||
await safeNavigateAndClick(page, "new-project-btn");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(500); // Brief pause between navigation steps
|
||||
|
||||
// Click on the specific template
|
||||
await page.getByTestId("text_card_container").nth(template.index).click();
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
// Ensure templates are fully loaded and stable before clicking
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
await page.waitForSelector('[data-testid="div-generic-node"]', {
|
||||
timeout: 15000,
|
||||
});
|
||||
// Wait for the specific template to be visible and stable
|
||||
const targetTemplate = page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(template.index);
|
||||
await expect(targetTemplate).toBeVisible({ timeout: 15000 });
|
||||
await page.waitForTimeout(300); // Ensure element is stable
|
||||
|
||||
if ((await page.getByTestId("update-all-button").count()) > 0) {
|
||||
await targetTemplate.click();
|
||||
|
||||
// Wait for canvas to load properly with enhanced stability checks
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
|
||||
// Wait for canvas elements to be visible and stable
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="div-generic-node"]',
|
||||
25000,
|
||||
);
|
||||
|
||||
// Additional wait to ensure all canvas components are fully rendered
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Use auto-retrying assertion for checking outdated components
|
||||
const updateButtonCount = await page
|
||||
.getByTestId("update-all-button")
|
||||
.count();
|
||||
if (updateButtonCount > 0) {
|
||||
console.error(`
|
||||
---------------------------------------------------------------------------------------
|
||||
There's an outdated component on the basic template: ${template.name}
|
||||
@ -73,6 +156,9 @@ test(
|
||||
`);
|
||||
numberOfOutdatedComponents++;
|
||||
}
|
||||
|
||||
// Longer delay between template tests to prevent race conditions
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
expect(numberOfOutdatedComponents).toBe(0);
|
||||
|
||||
@ -1,6 +1,35 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
// Helper function to ensure element stability before interaction
|
||||
async function waitForElementStability(
|
||||
page: any,
|
||||
selector: string,
|
||||
timeout = 5000,
|
||||
) {
|
||||
await page.waitForSelector(selector, { state: "visible", timeout });
|
||||
// Additional wait to ensure element is stable and not moving
|
||||
await page.waitForTimeout(200);
|
||||
// Use first() to avoid strict mode violation when multiple elements exist
|
||||
await expect(page.locator(selector).first()).toBeVisible();
|
||||
}
|
||||
|
||||
// Helper function to safely navigate with proper waiting
|
||||
async function safeNavigateAndClick(
|
||||
page: any,
|
||||
testId: string,
|
||||
waitForSelector?: string,
|
||||
) {
|
||||
const element = page.getByTestId(testId);
|
||||
await expect(element).toBeVisible({ timeout: 15000 });
|
||||
await element.click();
|
||||
|
||||
if (waitForSelector) {
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await waitForElementStability(page, waitForSelector);
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"user should be able to use first quarter of starter projects without any outdated components on the flow",
|
||||
{ tag: ["@release", "@components"] },
|
||||
@ -10,12 +39,33 @@ test(
|
||||
const templatesData = [];
|
||||
let numberOfOutdatedComponents = 0;
|
||||
|
||||
// First, collect all template data
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
// First, collect all template data with proper waiting
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
|
||||
const numberOfTemplates = await page
|
||||
// Wait for templates to fully load and stabilize
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
// Ensure template count is stable by checking multiple times
|
||||
let numberOfTemplates = await page
|
||||
.getByTestId("text_card_container")
|
||||
.count();
|
||||
await page.waitForTimeout(1000); // Allow any dynamic loading to complete
|
||||
const verifyCount = await page.getByTestId("text_card_container").count();
|
||||
|
||||
// If counts don't match, wait a bit more and try again
|
||||
if (numberOfTemplates !== verifyCount) {
|
||||
await page.waitForTimeout(2000);
|
||||
numberOfTemplates = await page.getByTestId("text_card_container").count();
|
||||
}
|
||||
|
||||
const firstQuarterEnd = Math.ceil(numberOfTemplates / 4);
|
||||
|
||||
@ -23,13 +73,13 @@ test(
|
||||
`Total templates: ${numberOfTemplates}, Testing from 0 to ${firstQuarterEnd - 1} (first quarter)`,
|
||||
);
|
||||
|
||||
// Collect template names first
|
||||
// Collect template names first with stability checks
|
||||
for (let i = 0; i < firstQuarterEnd; i++) {
|
||||
const exampleName = await page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(i)
|
||||
.getAttribute("role");
|
||||
// Ensure the specific template card is visible and stable
|
||||
const templateCard = page.getByTestId("text_card_container").nth(i);
|
||||
await expect(templateCard).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const exampleName = await templateCard.getAttribute("role");
|
||||
templatesData.push({ index: i, name: exampleName });
|
||||
}
|
||||
|
||||
@ -42,29 +92,62 @@ test(
|
||||
for (const template of templatesData) {
|
||||
console.log(`Testing template ${template.index}: ${template.name}`);
|
||||
|
||||
// Navigate directly to templates page
|
||||
await page.goto("/");
|
||||
// Navigate directly to templates page with improved stability
|
||||
await page.goto("/", { waitUntil: "networkidle", timeout: 30000 });
|
||||
|
||||
// Wait for main page to be fully loaded and stable
|
||||
await expect(page.getByTestId("mainpage_title")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("new-project-btn").first().click();
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Wait for templates to load
|
||||
await page.waitForSelector('[data-testid="text_card_container"]', {
|
||||
timeout: 10000,
|
||||
});
|
||||
// Sequential navigation with proper waiting (no Promise.all)
|
||||
await safeNavigateAndClick(page, "new-project-btn");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(500); // Brief pause between navigation steps
|
||||
|
||||
// Click on the specific template
|
||||
await page.getByTestId("text_card_container").nth(template.index).click();
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
// Ensure templates are fully loaded and stable before clicking
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
await page.waitForSelector('[data-testid="div-generic-node"]', {
|
||||
timeout: 15000,
|
||||
});
|
||||
// Wait for the specific template to be visible and stable
|
||||
const targetTemplate = page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(template.index);
|
||||
await expect(targetTemplate).toBeVisible({ timeout: 15000 });
|
||||
await page.waitForTimeout(300); // Ensure element is stable
|
||||
|
||||
if ((await page.getByTestId("update-all-button").count()) > 0) {
|
||||
await targetTemplate.click();
|
||||
|
||||
// Wait for canvas to load properly with enhanced stability checks
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
|
||||
// Wait for canvas elements to be visible and stable
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="div-generic-node"]',
|
||||
25000,
|
||||
);
|
||||
|
||||
// Additional wait to ensure all canvas components are fully rendered
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Use auto-retrying assertion for checking outdated components
|
||||
const updateButtonCount = await page
|
||||
.getByTestId("update-all-button")
|
||||
.count();
|
||||
if (updateButtonCount > 0) {
|
||||
console.error(`
|
||||
---------------------------------------------------------------------------------------
|
||||
There's an outdated component on the basic template: ${template.name}
|
||||
@ -72,6 +155,9 @@ test(
|
||||
`);
|
||||
numberOfOutdatedComponents++;
|
||||
}
|
||||
|
||||
// Longer delay between template tests to prevent race conditions
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
expect(numberOfOutdatedComponents).toBe(0);
|
||||
|
||||
@ -1,6 +1,35 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
// Helper function to ensure element stability before interaction
|
||||
async function waitForElementStability(
|
||||
page: any,
|
||||
selector: string,
|
||||
timeout = 5000,
|
||||
) {
|
||||
await page.waitForSelector(selector, { state: "visible", timeout });
|
||||
// Additional wait to ensure element is stable and not moving
|
||||
await page.waitForTimeout(200);
|
||||
// Use first() to avoid strict mode violation when multiple elements exist
|
||||
await expect(page.locator(selector).first()).toBeVisible();
|
||||
}
|
||||
|
||||
// Helper function to safely navigate with proper waiting
|
||||
async function safeNavigateAndClick(
|
||||
page: any,
|
||||
testId: string,
|
||||
waitForSelector?: string,
|
||||
) {
|
||||
const element = page.getByTestId(testId);
|
||||
await expect(element).toBeVisible({ timeout: 15000 });
|
||||
await element.click();
|
||||
|
||||
if (waitForSelector) {
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await waitForElementStability(page, waitForSelector);
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"user should be able to use second quarter of starter projects without any outdated components on the flow",
|
||||
{ tag: ["@release", "@components"] },
|
||||
@ -10,12 +39,33 @@ test(
|
||||
const templatesData = [];
|
||||
let numberOfOutdatedComponents = 0;
|
||||
|
||||
// First, collect all template data
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
// First, collect all template data with proper waiting
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
|
||||
const numberOfTemplates = await page
|
||||
// Wait for templates to fully load and stabilize
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
// Ensure template count is stable by checking multiple times
|
||||
let numberOfTemplates = await page
|
||||
.getByTestId("text_card_container")
|
||||
.count();
|
||||
await page.waitForTimeout(1000); // Allow any dynamic loading to complete
|
||||
const verifyCount = await page.getByTestId("text_card_container").count();
|
||||
|
||||
// If counts don't match, wait a bit more and try again
|
||||
if (numberOfTemplates !== verifyCount) {
|
||||
await page.waitForTimeout(2000);
|
||||
numberOfTemplates = await page.getByTestId("text_card_container").count();
|
||||
}
|
||||
|
||||
const firstQuarterEnd = Math.ceil(numberOfTemplates / 4);
|
||||
const secondQuarterEnd = Math.ceil((numberOfTemplates * 2) / 4);
|
||||
@ -24,13 +74,13 @@ test(
|
||||
`Total templates: ${numberOfTemplates}, Testing from ${firstQuarterEnd} to ${secondQuarterEnd - 1} (second quarter)`,
|
||||
);
|
||||
|
||||
// Collect template names first
|
||||
// Collect template names first with stability checks
|
||||
for (let i = firstQuarterEnd; i < secondQuarterEnd; i++) {
|
||||
const exampleName = await page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(i)
|
||||
.getAttribute("role");
|
||||
// Ensure the specific template card is visible and stable
|
||||
const templateCard = page.getByTestId("text_card_container").nth(i);
|
||||
await expect(templateCard).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const exampleName = await templateCard.getAttribute("role");
|
||||
templatesData.push({ index: i, name: exampleName });
|
||||
}
|
||||
|
||||
@ -43,29 +93,62 @@ test(
|
||||
for (const template of templatesData) {
|
||||
console.log(`Testing template ${template.index}: ${template.name}`);
|
||||
|
||||
// Navigate directly to templates page
|
||||
await page.goto("/");
|
||||
// Navigate directly to templates page with improved stability
|
||||
await page.goto("/", { waitUntil: "networkidle", timeout: 30000 });
|
||||
|
||||
// Wait for main page to be fully loaded and stable
|
||||
await expect(page.getByTestId("mainpage_title")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("new-project-btn").first().click();
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Wait for templates to load
|
||||
await page.waitForSelector('[data-testid="text_card_container"]', {
|
||||
timeout: 10000,
|
||||
});
|
||||
// Sequential navigation with proper waiting (no Promise.all)
|
||||
await safeNavigateAndClick(page, "new-project-btn");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(500); // Brief pause between navigation steps
|
||||
|
||||
// Click on the specific template
|
||||
await page.getByTestId("text_card_container").nth(template.index).click();
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
// Ensure templates are fully loaded and stable before clicking
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
await page.waitForSelector('[data-testid="div-generic-node"]', {
|
||||
timeout: 15000,
|
||||
});
|
||||
// Wait for the specific template to be visible and stable
|
||||
const targetTemplate = page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(template.index);
|
||||
await expect(targetTemplate).toBeVisible({ timeout: 15000 });
|
||||
await page.waitForTimeout(300); // Ensure element is stable
|
||||
|
||||
if ((await page.getByTestId("update-all-button").count()) > 0) {
|
||||
await targetTemplate.click();
|
||||
|
||||
// Wait for canvas to load properly with enhanced stability checks
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
|
||||
// Wait for canvas elements to be visible and stable
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="div-generic-node"]',
|
||||
25000,
|
||||
);
|
||||
|
||||
// Additional wait to ensure all canvas components are fully rendered
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Use auto-retrying assertion for checking outdated components
|
||||
const updateButtonCount = await page
|
||||
.getByTestId("update-all-button")
|
||||
.count();
|
||||
if (updateButtonCount > 0) {
|
||||
console.error(`
|
||||
---------------------------------------------------------------------------------------
|
||||
There's an outdated component on the basic template: ${template.name}
|
||||
@ -73,6 +156,9 @@ test(
|
||||
`);
|
||||
numberOfOutdatedComponents++;
|
||||
}
|
||||
|
||||
// Longer delay between template tests to prevent race conditions
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
expect(numberOfOutdatedComponents).toBe(0);
|
||||
|
||||
@ -1,6 +1,35 @@
|
||||
import { expect, test } from "../../fixtures";
|
||||
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||
|
||||
// Helper function to ensure element stability before interaction
|
||||
async function waitForElementStability(
|
||||
page: any,
|
||||
selector: string,
|
||||
timeout = 5000,
|
||||
) {
|
||||
await page.waitForSelector(selector, { state: "visible", timeout });
|
||||
// Additional wait to ensure element is stable and not moving
|
||||
await page.waitForTimeout(200);
|
||||
// Use first() to avoid strict mode violation when multiple elements exist
|
||||
await expect(page.locator(selector).first()).toBeVisible();
|
||||
}
|
||||
|
||||
// Helper function to safely navigate with proper waiting
|
||||
async function safeNavigateAndClick(
|
||||
page: any,
|
||||
testId: string,
|
||||
waitForSelector?: string,
|
||||
) {
|
||||
const element = page.getByTestId(testId);
|
||||
await expect(element).toBeVisible({ timeout: 15000 });
|
||||
await element.click();
|
||||
|
||||
if (waitForSelector) {
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await waitForElementStability(page, waitForSelector);
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"user should be able to use fourth quarter of starter projects without any outdated components on the flow",
|
||||
{ tag: ["@release", "@components"] },
|
||||
@ -10,12 +39,33 @@ test(
|
||||
const templatesData = [];
|
||||
let numberOfOutdatedComponents = 0;
|
||||
|
||||
// First, collect all template data
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
// First, collect all template data with proper waiting
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
|
||||
const numberOfTemplates = await page
|
||||
// Wait for templates to fully load and stabilize
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
// Ensure template count is stable by checking multiple times
|
||||
let numberOfTemplates = await page
|
||||
.getByTestId("text_card_container")
|
||||
.count();
|
||||
await page.waitForTimeout(1000); // Allow any dynamic loading to complete
|
||||
const verifyCount = await page.getByTestId("text_card_container").count();
|
||||
|
||||
// If counts don't match, wait a bit more and try again
|
||||
if (numberOfTemplates !== verifyCount) {
|
||||
await page.waitForTimeout(2000);
|
||||
numberOfTemplates = await page.getByTestId("text_card_container").count();
|
||||
}
|
||||
|
||||
const thirdQuarterEnd = Math.ceil((numberOfTemplates * 3) / 4);
|
||||
|
||||
@ -23,13 +73,13 @@ test(
|
||||
`Total templates: ${numberOfTemplates}, Testing from ${thirdQuarterEnd} to ${numberOfTemplates - 1} (fourth quarter)`,
|
||||
);
|
||||
|
||||
// Collect template names first
|
||||
// Collect template names first with stability checks
|
||||
for (let i = thirdQuarterEnd; i < numberOfTemplates; i++) {
|
||||
const exampleName = await page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(i)
|
||||
.getAttribute("role");
|
||||
// Ensure the specific template card is visible and stable
|
||||
const templateCard = page.getByTestId("text_card_container").nth(i);
|
||||
await expect(templateCard).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const exampleName = await templateCard.getAttribute("role");
|
||||
templatesData.push({ index: i, name: exampleName });
|
||||
}
|
||||
|
||||
@ -42,29 +92,62 @@ test(
|
||||
for (const template of templatesData) {
|
||||
console.log(`Testing template ${template.index}: ${template.name}`);
|
||||
|
||||
// Navigate directly to templates page
|
||||
await page.goto("/");
|
||||
// Navigate directly to templates page with improved stability
|
||||
await page.goto("/", { waitUntil: "networkidle", timeout: 30000 });
|
||||
|
||||
// Wait for main page to be fully loaded and stable
|
||||
await expect(page.getByTestId("mainpage_title")).toBeVisible({
|
||||
timeout: 30000,
|
||||
});
|
||||
await page.getByTestId("new-project-btn").first().click();
|
||||
await page.getByTestId("side_nav_options_all-templates").click();
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
// Wait for templates to load
|
||||
await page.waitForSelector('[data-testid="text_card_container"]', {
|
||||
timeout: 10000,
|
||||
});
|
||||
// Sequential navigation with proper waiting (no Promise.all)
|
||||
await safeNavigateAndClick(page, "new-project-btn");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(500); // Brief pause between navigation steps
|
||||
|
||||
// Click on the specific template
|
||||
await page.getByTestId("text_card_container").nth(template.index).click();
|
||||
await safeNavigateAndClick(
|
||||
page,
|
||||
"side_nav_options_all-templates",
|
||||
'[data-testid="text_card_container"]',
|
||||
);
|
||||
await page.waitForLoadState("networkidle");
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
// Ensure templates are fully loaded and stable before clicking
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="text_card_container"]',
|
||||
20000,
|
||||
);
|
||||
|
||||
await page.waitForSelector('[data-testid="div-generic-node"]', {
|
||||
timeout: 15000,
|
||||
});
|
||||
// Wait for the specific template to be visible and stable
|
||||
const targetTemplate = page
|
||||
.getByTestId("text_card_container")
|
||||
.nth(template.index);
|
||||
await expect(targetTemplate).toBeVisible({ timeout: 15000 });
|
||||
await page.waitForTimeout(300); // Ensure element is stable
|
||||
|
||||
if ((await page.getByTestId("update-all-button").count()) > 0) {
|
||||
await targetTemplate.click();
|
||||
|
||||
// Wait for canvas to load properly with enhanced stability checks
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForLoadState("networkidle", { timeout: 30000 });
|
||||
|
||||
// Wait for canvas elements to be visible and stable
|
||||
await waitForElementStability(
|
||||
page,
|
||||
'[data-testid="div-generic-node"]',
|
||||
25000,
|
||||
);
|
||||
|
||||
// Additional wait to ensure all canvas components are fully rendered
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
// Use auto-retrying assertion for checking outdated components
|
||||
const updateButtonCount = await page
|
||||
.getByTestId("update-all-button")
|
||||
.count();
|
||||
if (updateButtonCount > 0) {
|
||||
console.error(`
|
||||
---------------------------------------------------------------------------------------
|
||||
There's an outdated component on the basic template: ${template.name}
|
||||
@ -72,6 +155,9 @@ test(
|
||||
`);
|
||||
numberOfOutdatedComponents++;
|
||||
}
|
||||
|
||||
// Longer delay between template tests to prevent race conditions
|
||||
await page.waitForTimeout(1000);
|
||||
}
|
||||
|
||||
expect(numberOfOutdatedComponents).toBe(0);
|
||||
|
||||
Reference in New Issue
Block a user