From 66d051ec280ae279cf0d5ae19d1d8109ca929386 Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Tue, 10 Mar 2026 21:56:40 +0100 Subject: [PATCH] test(e2e): fix flaky project creation and context switching tests Replace broken getByRole selector for project name input with a direct DOM selector, replace hardcoded waitForTimeout calls with proper element-based waits (waitForTaskList, waitFor visible, toBeEnabled). --- e2e/pages/project.page.ts | 19 +++++++++++++------ .../navigation/context-switching.spec.ts | 18 +++++++----------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/e2e/pages/project.page.ts b/e2e/pages/project.page.ts index b67ec2f8fb..98d259e7fc 100644 --- a/e2e/pages/project.page.ts +++ b/e2e/pages/project.page.ts @@ -21,7 +21,9 @@ export class ProjectPage extends BasePage { 'button[aria-label="Create New Project"], button:has-text("Create Project")', ); this.projectAccordion = page.locator('nav-item button:has-text("Projects")'); - this.projectNameInput = page.getByRole('textbox', { name: 'Project Name' }); + this.projectNameInput = page + .locator('dialog-create-project input[type="text"]') + .first(); this.submitBtn = page.locator('dialog-create-project button[type=submit]'); this.workCtxMenu = page.locator('work-context-menu'); // Use more specific selector to avoid matching titles in other areas @@ -109,11 +111,13 @@ export class ProjectPage extends BasePage { // Wait for the dialog to appear and be fully initialized await this.projectNameInput.waitFor({ state: 'visible', timeout: 10000 }); - // Fill has built-in waiting for element to be editable, but Angular forms - // need a moment to wire up. Add a small delay for form initialization. - await this.page.waitForTimeout(500); + // Wait for Angular form to wire up by checking the submit button is present + await this.submitBtn.waitFor({ state: 'visible', timeout: 5000 }); await this.projectNameInput.fill(prefixedProjectName); + + // Wait for the submit button to be enabled (Angular form validation completes) + await expect(this.submitBtn).toBeEnabled({ timeout: 5000 }); await this.submitBtn.click(); // Wait for dialog to close by waiting for input to be hidden @@ -132,8 +136,11 @@ export class ProjectPage extends BasePage { async navigateToProjectByName(projectName: string): Promise { const fullProjectName = this.applyPrefix(projectName); - // Wait for Angular to fully render after any navigation - await this.page.waitForTimeout(2000); + // Wait for the route wrapper to be visible (Angular navigation complete) + await this.page + .locator('.route-wrapper, main, [role="main"]') + .first() + .waitFor({ state: 'visible', timeout: 10000 }); // Helper function to check if we're already on the project const isAlreadyOnProject = async (): Promise => { diff --git a/e2e/tests/navigation/context-switching.spec.ts b/e2e/tests/navigation/context-switching.spec.ts index 22dcbd4f18..cd02b7fd7d 100644 --- a/e2e/tests/navigation/context-switching.spec.ts +++ b/e2e/tests/navigation/context-switching.spec.ts @@ -37,7 +37,7 @@ test.describe('Context Switching', () => { // Switch to second project await projectPage.navigateToProjectByName(project2); - await page.waitForTimeout(500); + await workViewPage.waitForTaskList(); // Task from project 1 should not be visible await expect(task1).not.toBeVisible(); @@ -49,7 +49,7 @@ test.describe('Context Switching', () => { // Switch back to project 1 await projectPage.navigateToProjectByName(project1); - await page.waitForTimeout(500); + await workViewPage.waitForTaskList(); // Task from project 1 should be visible again await expect(task1).toBeVisible(); @@ -92,19 +92,17 @@ test.describe('Context Switching', () => { const isExpanded = await tagsGroupBtn.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await tagsGroupBtn.click(); - await page.waitForTimeout(500); } - // Click on the tag + // Click on the tag - wait for it to appear after expansion const tagTreeItem = tagPage.tagsGroup .locator('[role="treeitem"]') .filter({ hasText: tagName }) .first(); + await tagTreeItem.waitFor({ state: 'visible', timeout: 5000 }); await tagTreeItem.click(); - await page.waitForTimeout(500); - - // Verify URL changed to tag view await expect(page).toHaveURL(/tag/); + await workViewPage.waitForTaskList(); // Project task should not be visible (unless also assigned to this tag) await expect(projectTask).not.toBeVisible(); @@ -131,18 +129,16 @@ test.describe('Context Switching', () => { const isExpanded = await tagsGroupBtn.getAttribute('aria-expanded'); if (isExpanded !== 'true') { await tagsGroupBtn.click(); - await page.waitForTimeout(500); } const tagTreeItem = tagPage.tagsGroup .locator('[role="treeitem"]') .filter({ hasText: tagName }) .first(); + await tagTreeItem.waitFor({ state: 'visible', timeout: 5000 }); await tagTreeItem.click(); - await page.waitForTimeout(500); - - // Verify on tag view await expect(page).toHaveURL(/tag/); + await workViewPage.waitForTaskList(); // Navigate back to TODAY tag by clicking "Today" in sidebar const todayNavItem = page.locator(