diff --git a/e2e/tests/sync/import-sync.spec.ts b/e2e/tests/sync/import-sync.spec.ts index a448b7859f..ad7aefe97f 100644 --- a/e2e/tests/sync/import-sync.spec.ts +++ b/e2e/tests/sync/import-sync.spec.ts @@ -17,7 +17,7 @@ import { ImportPage } from '../../pages/import.page'; * This includes active tasks, archived tasks (worklog), and projects. * * Prerequisites: - * - super-sync-server running on localhost:1900 with TEST_MODE=true + * - super-sync-server running on localhost:1901 with TEST_MODE=true * - Frontend running on localhost:4242 * * Run with: npm run e2e:playwright -- --grep @importsync @@ -35,7 +35,7 @@ base.describe('@importsync @supersync Import + Sync E2E', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-advanced-edge-cases.spec.ts b/e2e/tests/sync/supersync-advanced-edge-cases.spec.ts index 5f250f64f0..e9fc20c9a4 100644 --- a/e2e/tests/sync/supersync-advanced-edge-cases.spec.ts +++ b/e2e/tests/sync/supersync-advanced-edge-cases.spec.ts @@ -33,7 +33,7 @@ base.describe('@supersync SuperSync Advanced Edge Cases', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-advanced.spec.ts b/e2e/tests/sync/supersync-advanced.spec.ts index a26f39b4c9..4f26a24f57 100644 --- a/e2e/tests/sync/supersync-advanced.spec.ts +++ b/e2e/tests/sync/supersync-advanced.spec.ts @@ -29,7 +29,7 @@ base.describe('@supersync SuperSync Advanced', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-daily-summary.spec.ts b/e2e/tests/sync/supersync-daily-summary.spec.ts index db0d753a0e..2cc571daef 100644 --- a/e2e/tests/sync/supersync-daily-summary.spec.ts +++ b/e2e/tests/sync/supersync-daily-summary.spec.ts @@ -33,7 +33,7 @@ base.describe('@supersync Daily Summary Sync', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-edge-cases.spec.ts b/e2e/tests/sync/supersync-edge-cases.spec.ts index 77854d0c66..69247ee0bd 100644 --- a/e2e/tests/sync/supersync-edge-cases.spec.ts +++ b/e2e/tests/sync/supersync-edge-cases.spec.ts @@ -88,7 +88,7 @@ base.describe('@supersync SuperSync Edge Cases', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-encryption.spec.ts b/e2e/tests/sync/supersync-encryption.spec.ts index 114f50b5a4..2ec2eba90b 100644 --- a/e2e/tests/sync/supersync-encryption.spec.ts +++ b/e2e/tests/sync/supersync-encryption.spec.ts @@ -53,7 +53,7 @@ base.describe('@supersync SuperSync Encryption', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-late-join.spec.ts b/e2e/tests/sync/supersync-late-join.spec.ts index a47a4a7f24..b90a1addfb 100644 --- a/e2e/tests/sync/supersync-late-join.spec.ts +++ b/e2e/tests/sync/supersync-late-join.spec.ts @@ -27,7 +27,7 @@ base.describe('@supersync SuperSync Late Join', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } @@ -92,37 +92,45 @@ base.describe('@supersync SuperSync Late Join', () => { // Since B has local data and server has remote data, and they might touch global settings or similar, // a conflict might occur. However, tasks are distinct IDs, so they should merge cleanly. // If a conflict dialog appears for Global Config or similar, we should handle it. - // The helper `syncAndWait` doesn't handle conflicts automatically, but the tests in `supersync.spec.ts` - // seem to expect `Conflict dialog detected` in logs and handle it? - // Let's check if conflict dialog is visible and resolve it if so. - // Usually "Use Remote" or "Use Local" is fine if keys don't overlap. - // For distinct tasks, no conflict should occur on the tasks themselves. - + // Check multiple times as dialog might appear with slight delay const conflictDialog = clientB.page.locator('dialog-conflict-resolution'); - if (await conflictDialog.isVisible({ timeout: 2000 })) { - console.log('Conflict dialog detected on B, resolving...'); - // Click "Use Local" or "Use Remote" or "Merge" - // Since we want to keep B's local tasks and A's remote tasks, valid merge should happen. - // But if it's a conflict on a singleton model (like Global Config), we just pick one. - // Let's pick Remote for config. - const useRemoteBtn = conflictDialog - .locator('button') - .filter({ hasText: 'Remote' }) - .first(); - if (await useRemoteBtn.isVisible()) { - await useRemoteBtn.click(); - } else { - // Fallback - await clientB.page.keyboard.press('Escape'); + for (let attempt = 0; attempt < 3; attempt++) { + try { + await conflictDialog.waitFor({ state: 'visible', timeout: 2000 }); + console.log('Conflict dialog detected on B, resolving...'); + // Pick Remote for singleton models (like Global Config) + const useRemoteBtn = conflictDialog + .locator('button') + .filter({ hasText: 'Remote' }) + .first(); + if (await useRemoteBtn.isVisible()) { + await useRemoteBtn.click(); + // Wait for dialog to close + await conflictDialog.waitFor({ state: 'hidden', timeout: 5000 }); + } else { + // Fallback: dismiss dialog + await clientB.page.keyboard.press('Escape'); + } + // Brief wait for any subsequent dialogs + await clientB.page.waitForTimeout(500); + } catch { + // No conflict dialog visible, proceed + break; } } - // Wait for sync to settle - await clientB.page.waitForTimeout(1000); + // Wait for sync to fully settle after conflict resolution + await clientB.page.waitForTimeout(2000); + + // Trigger another sync to ensure all data is propagated + await clientB.sync.syncAndWait(); // A Syncs to get B's data await clientA.sync.syncAndWait(); + // Brief wait for state to propagate + await clientA.page.waitForTimeout(1000); + // VERIFICATION // Both clients should have A1, A2, A3, B1, B2, B3 diff --git a/e2e/tests/sync/supersync-lww-conflict.spec.ts b/e2e/tests/sync/supersync-lww-conflict.spec.ts index db967a837c..5b0a5de1dc 100644 --- a/e2e/tests/sync/supersync-lww-conflict.spec.ts +++ b/e2e/tests/sync/supersync-lww-conflict.spec.ts @@ -32,7 +32,7 @@ base.describe('@supersync SuperSync LWW Conflict Resolution', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-models.spec.ts b/e2e/tests/sync/supersync-models.spec.ts index 4013f42dea..952d31c0ea 100644 --- a/e2e/tests/sync/supersync-models.spec.ts +++ b/e2e/tests/sync/supersync-models.spec.ts @@ -155,7 +155,7 @@ base.describe('@supersync SuperSync Models', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } @@ -259,62 +259,57 @@ base.describe('@supersync SuperSync Models', () => { // Create task await clientA.workView.addTask(taskName); - // Add tag to task via task detail + // Helper to dismiss all overlays reliably + const dismissAllOverlays = async (): Promise => { + // Dismiss backdrops by pressing Escape multiple times + for (let j = 0; j < 3; j++) { + await clientA.page.keyboard.press('Escape'); + await clientA.page.waitForTimeout(100); + } + // Wait for any overlays to disappear + const overlayBackdrop = clientA.page.locator('.cdk-overlay-backdrop'); + try { + await overlayBackdrop.waitFor({ state: 'hidden', timeout: 2000 }); + } catch { + // If still visible, force click to dismiss + if (await overlayBackdrop.isVisible()) { + await overlayBackdrop.click({ force: true }); + await clientA.page.waitForTimeout(200); + } + } + }; + + // Dismiss any existing overlays first + await dismissAllOverlays(); + + // Add tag to task via context menu (most reliable method) const taskLocator = clientA.page.locator(`task:has-text("${taskName}")`); - await taskLocator.click(); + await taskLocator.waitFor({ state: 'visible' }); - // Hover to ensure buttons appear - await taskLocator.hover(); - await clientA.page.waitForTimeout(500); - - // Click "Edit Tags" button - const editTagsBtn = clientA.page - .locator('button[title="Edit tags"], button[aria-label="Edit tags"]') - .first(); - - // Retry loop for opening tags menu + // Retry loop for opening tags menu via context menu let menuOpened = false; for (let i = 0; i < 3; i++) { try { - if (await editTagsBtn.isVisible()) { - await editTagsBtn.click(); - } else { - // Fallback: Try context menu - await taskLocator.click({ button: 'right' }); - // The context menu item is named "Toggle Tags" in English (from T.F.TASK.CMP.TOGGLE_TAGS) - // But we also check for "Edit tags" just in case - const ctxEditTags = clientA.page.locator('.e2e-edit-tags-btn').first(); + // Ensure clean state before each attempt + await dismissAllOverlays(); - if (await ctxEditTags.isVisible()) { - await ctxEditTags.click(); - } else { - // Last resort: shortcut - console.log('Context menu item not found, trying shortcut...'); - // Close context menu if open - const backdrop = clientA.page.locator('.cdk-overlay-backdrop'); - if (await backdrop.isVisible()) { - await backdrop.click({ force: true }); - } else { - await clientA.page.keyboard.press('Escape'); - } - await clientA.page.waitForTimeout(200); + // Right-click on task to open context menu (doesn't trigger edit mode) + await taskLocator.click({ button: 'right' }); + await clientA.page.waitForTimeout(300); - // Ensure task is focused/selected - await taskLocator.click({ force: true }); - await clientA.page.waitForTimeout(200); - // Use keyboard shortcut to open the tag menu (default: "G") - await clientA.page.keyboard.press('g'); - } - } + // Find and click the "Edit tags" button in context menu + const ctxEditTags = clientA.page.locator('.e2e-edit-tags-btn').first(); + await ctxEditTags.waitFor({ state: 'visible', timeout: 3000 }); + await ctxEditTags.click(); - // Dialog "Edit Tags" - now it's a menu - const menuPanel = clientA.page.locator('.mat-mdc-menu-panel'); - await menuPanel.waitFor({ state: 'visible', timeout: 3000 }); + // Wait for tag submenu to appear + const menuPanel = clientA.page.locator('.mat-mdc-menu-panel').last(); + await menuPanel.waitFor({ state: 'visible', timeout: 5000 }); menuOpened = true; - break; // Success + break; } catch (e) { console.log(`Attempt ${i + 1} to open tags menu failed: ${e}, retrying...`); - await clientA.page.keyboard.press('Escape'); // Reset state + await dismissAllOverlays(); await clientA.page.waitForTimeout(500); } } @@ -323,23 +318,18 @@ base.describe('@supersync SuperSync Models', () => { throw new Error('Failed to open tags menu after 3 attempts'); } - const menuPanel = clientA.page.locator('.mat-mdc-menu-panel'); - // Select the tag from list - const tagOption = menuPanel + // Select the tag from the submenu (last menu panel is the tag submenu) + const tagMenu = clientA.page.locator('.mat-mdc-menu-panel').last(); + const tagOption = tagMenu .locator('button[mat-menu-item]') .filter({ hasText: tagName }) .first(); - if (await tagOption.isVisible()) { - await tagOption.click(); - } else { - console.log('Tag not found in menu, creating new via prompt if needed'); - // If not found, maybe we need to scroll or it's not in the list - } + await tagOption.waitFor({ state: 'visible', timeout: 3000 }); + await tagOption.click(); - // Close menu if still open (clicking option might close it) - if (await menuPanel.isVisible()) { - await clientA.page.keyboard.press('Escape'); - } + // Wait for menus to close after selection + await clientA.page.waitForTimeout(500); + await dismissAllOverlays(); // Ensure tag is shown on Client A before syncing await expect(taskLocator).toContainText(tagName); diff --git a/e2e/tests/sync/supersync-repeat-task.spec.ts b/e2e/tests/sync/supersync-repeat-task.spec.ts index ef2040e7f0..779d07bbf8 100644 --- a/e2e/tests/sync/supersync-repeat-task.spec.ts +++ b/e2e/tests/sync/supersync-repeat-task.spec.ts @@ -65,7 +65,7 @@ base.describe('@supersync SuperSync Repeatable Task Sync', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync-server-migration.spec.ts b/e2e/tests/sync/supersync-server-migration.spec.ts index e789193a1e..612e073f3b 100644 --- a/e2e/tests/sync/supersync-server-migration.spec.ts +++ b/e2e/tests/sync/supersync-server-migration.spec.ts @@ -38,7 +38,7 @@ base.describe.serial('@supersync SuperSync Server Migration', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } } diff --git a/e2e/tests/sync/supersync.spec.ts b/e2e/tests/sync/supersync.spec.ts index 546c9cf6ca..001be658cc 100644 --- a/e2e/tests/sync/supersync.spec.ts +++ b/e2e/tests/sync/supersync.spec.ts @@ -18,7 +18,7 @@ import { waitForAppReady } from '../../utils/waits'; * the full stack including UI, network, and real IndexedDB isolation. * * Prerequisites: - * - super-sync-server running on localhost:1900 with TEST_MODE=true + * - super-sync-server running on localhost:1901 with TEST_MODE=true * - Frontend running on localhost:4242 * * Run with: npm run e2e:supersync @@ -41,7 +41,7 @@ base.describe('@supersync SuperSync E2E', () => { serverHealthy = await isServerHealthy(); if (!serverHealthy) { console.warn( - 'SuperSync server not healthy at http://localhost:1900 - skipping tests', + 'SuperSync server not healthy at http://localhost:1901 - skipping tests', ); } }