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:
Cristhian Zanforlin Lousa
2025-10-01 17:34:28 -03:00
committed by GitHub
parent a7ecf04579
commit e61316fe95
7 changed files with 661 additions and 100 deletions

View File

@ -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(

View File

@ -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}"
)

View File

@ -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,
},
],
});

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);