test(e2e): improve sync test robustness with archive persistence waits

Adds explicit waits after archive operations to ensure IndexedDB writes
complete before proceeding with sync operations. This prevents race
conditions where sync attempts to read state before archive persistence
finishes.

Changes:
- Add waitForArchivePersistence() helper to sync-helpers.ts
  - Waits 1000ms for IndexedDB operations to complete
  - Additional 100ms for pending micro-tasks/animations
- Add 500ms waits in waitForSyncComplete() after detecting sync success
  - Ensures IndexedDB writes fully settle before returning
- Apply waitForArchivePersistence() in webdav-sync-archive.spec.ts
  - After Client A archives Task1
  - After Client B archives Task3
- Apply waitForArchivePersistence() in webdav-sync-delete-cascade.spec.ts
  - After Client A archives task (tag deletion test)
  - After Client B archives Task1 (concurrent archive test)

These changes address flakiness in CI environments where async IndexedDB
operations may not complete before the next test assertion.

Related to: Bug #5995, Bug #6044 (focus-mode test fixes)
This commit is contained in:
Johannes Millan 2026-01-19 22:06:03 +01:00
parent 66a0ab856e
commit a49a863a08
3 changed files with 29 additions and 0 deletions

View file

@ -11,6 +11,7 @@ import {
createSyncFolder,
waitForSyncComplete,
generateSyncFolderName,
waitForArchivePersistence,
} from '../../utils/sync-helpers';
/**
@ -145,6 +146,7 @@ test.describe('@webdav WebDAV Archive Sync', () => {
console.log('[Archive Diff] Client A marked Task1 done');
await archiveDoneTasks(pageA);
await waitForArchivePersistence(pageA);
await expect(pageA.locator('task')).toHaveCount(1); // Only Task2 remains
console.log('[Archive Diff] Client A archived Task1');
@ -168,6 +170,7 @@ test.describe('@webdav WebDAV Archive Sync', () => {
console.log('[Archive Diff] Client B marked Task3 done');
await archiveDoneTasks(pageB);
await waitForArchivePersistence(pageB);
// Note: Daily Summary flow automatically syncs after archiving.
// So Client B downloads Client A's archive of Task1 during this flow.
// Final state on Client B: only Task2 (both Task1 and Task3 archived)

View file

@ -14,6 +14,7 @@ import {
waitForSyncComplete,
generateSyncFolderName,
dismissTourIfVisible,
waitForArchivePersistence,
} from '../../utils/sync-helpers';
import { Page } from 'playwright';
@ -117,6 +118,7 @@ test.describe('@webdav WebDAV Delete Cascade Sync', () => {
await taskPageA.markTaskAsDone(task);
await pageA.waitForTimeout(300);
await archiveDoneTasks(pageA);
await waitForArchivePersistence(pageA);
await expect(pageA.locator('task')).toHaveCount(0);
console.log('[Delete Tag] Client A archived task');
@ -415,6 +417,7 @@ test.describe('@webdav WebDAV Delete Cascade Sync', () => {
await taskPageB.markTaskAsDone(task1OnB);
await pageB.waitForTimeout(300);
await archiveDoneTasks(pageB);
await waitForArchivePersistence(pageB);
await expect(pageB.locator('task')).toHaveCount(1); // Only Task2 visible
console.log('[Concurrent] Client B archived Task1 (not synced yet)');

View file

@ -165,6 +165,8 @@ export const waitForSyncComplete = async (
// The icon is small (10px) and absolutely positioned, so use count() check
const syncStateIcon = page.locator('.sync-btn mat-icon.sync-state-ico');
if ((await syncStateIcon.count()) > 0) {
// Add extra wait to ensure IndexedDB writes complete and state settles
await page.waitForTimeout(500);
return 'success';
}
@ -178,6 +180,8 @@ export const waitForSyncComplete = async (
// Final check - make sure sync button is still there and no error shown
const syncBtnVisible = await syncPage.syncBtn.isVisible().catch(() => false);
if (syncBtnVisible) {
// Add extra wait to ensure IndexedDB writes complete and state settles
await page.waitForTimeout(500);
return 'success';
}
}
@ -217,6 +221,25 @@ export const waitForSync = async (
syncPage: SyncPage,
): Promise<'success' | 'conflict' | void> => waitForSyncComplete(page, syncPage);
/**
* Waits for archive operations to complete and persist.
* Archive operations (finish day, archive task) involve async IndexedDB writes
* that may not complete immediately. This helper ensures state is stable before proceeding.
*
* @param page - Playwright page instance
* @param waitMs - Time to wait in milliseconds (default: 1000ms)
*/
export const waitForArchivePersistence = async (
page: Page,
waitMs: number = 1000,
): Promise<void> => {
// Wait for IndexedDB operations to complete
await page.waitForTimeout(waitMs);
// Additional check: wait for any pending micro-tasks/animations
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 100)));
};
/**
* Simulates network failure by aborting all WebDAV requests.
* Useful for testing offline/error scenarios.